mirror of
https://github.com/ankitects/anki.git
synced 2025-12-10 13:26:56 -05:00
deck -> collection
This commit is contained in:
parent
6e4e8249fb
commit
279a942642
32 changed files with 523 additions and 526 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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]
|
||||
|
|
|
|||
|
|
@ -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
|
||||
##########################################################################
|
||||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
46
anki/find.py
46
anki/find.py
|
|
@ -18,10 +18,10 @@ SEARCH_GROUP = 7
|
|||
# Tools
|
||||
##########################################################################
|
||||
|
||||
def fieldNames(deck, downcase=True):
|
||||
def fieldNames(col, downcase=True):
|
||||
fields = set()
|
||||
names = []
|
||||
for m in deck.models.all():
|
||||
for m in col.models.all():
|
||||
for f in m['flds']:
|
||||
if f['name'].lower() not in fields:
|
||||
names.append(f['name'])
|
||||
|
|
@ -35,8 +35,8 @@ def fieldNames(deck, downcase=True):
|
|||
|
||||
class Finder(object):
|
||||
|
||||
def __init__(self, deck):
|
||||
self.deck = deck
|
||||
def __init__(self, col):
|
||||
self.col = col
|
||||
|
||||
def findCards(self, query, full=False):
|
||||
"Return a list of card ids for QUERY."
|
||||
|
|
@ -47,8 +47,8 @@ class Finder(object):
|
|||
return []
|
||||
(q, args) = self._whereClause()
|
||||
query = self._orderedSelect(q)
|
||||
res = self.deck.db.list(query, **args)
|
||||
if self.deck.conf['sortBackwards']:
|
||||
res = self.col.db.list(query, **args)
|
||||
if self.col.conf['sortBackwards']:
|
||||
res.reverse()
|
||||
return res
|
||||
|
||||
|
|
@ -65,7 +65,7 @@ class Finder(object):
|
|||
return q, self.lims['args']
|
||||
|
||||
def _orderedSelect(self, lim):
|
||||
type = self.deck.conf['sortType']
|
||||
type = self.col.conf['sortType']
|
||||
if not type:
|
||||
return "select id from cards c where " + lim
|
||||
elif type.startswith("note"):
|
||||
|
|
@ -153,7 +153,7 @@ order by %s""" % (lim, sort)
|
|||
elif val == "suspended":
|
||||
cond = "queue = -1"
|
||||
elif val == "due":
|
||||
cond = "(queue = 2 and due <= %d)" % self.deck.sched.today
|
||||
cond = "(queue = 2 and due <= %d)" % self.col.sched.today
|
||||
elif val == "recent":
|
||||
cond = "c.id in (select id from cards order by mod desc limit 100)"
|
||||
if neg:
|
||||
|
|
@ -174,7 +174,7 @@ order by %s""" % (lim, sort)
|
|||
# in the future we may want to apply this at the end to speed up
|
||||
# the case where there are other limits
|
||||
nids = []
|
||||
for nid, flds in self.deck.db.execute(
|
||||
for nid, flds in self.col.db.execute(
|
||||
"select id, flds from notes"):
|
||||
if val in stripHTML(flds):
|
||||
nids.append(nid)
|
||||
|
|
@ -186,14 +186,14 @@ order by %s""" % (lim, sort)
|
|||
def _findModel(self, val, isNeg):
|
||||
extra = "not" if isNeg else ""
|
||||
ids = []
|
||||
for m in self.deck.models.all():
|
||||
for m in self.col.models.all():
|
||||
if m['name'].lower() == val:
|
||||
ids.append(m['id'])
|
||||
self.lims['note'].append("mid %s in %s" % (extra, ids2str(ids)))
|
||||
|
||||
def _findGroup(self, val, isNeg):
|
||||
extra = "!" if isNeg else ""
|
||||
id = self.deck.groups.id(val, create=False) or 0
|
||||
id = self.col.groups.id(val, create=False) or 0
|
||||
self.lims['card'].append("c.gid %s= %s" % (extra, id))
|
||||
|
||||
def _findTemplate(self, val, isNeg):
|
||||
|
|
@ -205,7 +205,7 @@ order by %s""" % (lim, sort)
|
|||
except:
|
||||
num = None
|
||||
lims = []
|
||||
for m in self.deck.models.all():
|
||||
for m in self.col.models.all():
|
||||
for t in m['tmpls']:
|
||||
# ordinal number?
|
||||
if num is not None and t['ord'] == num:
|
||||
|
|
@ -228,7 +228,7 @@ order by %s""" % (lim, sort)
|
|||
value = "%" + parts[1].replace("*", "%") + "%"
|
||||
# find models that have that field
|
||||
mods = {}
|
||||
for m in self.deck.models.all():
|
||||
for m in self.col.models.all():
|
||||
for f in m['flds']:
|
||||
if f['name'].lower() == field:
|
||||
mods[m['id']] = (m, f['ord'])
|
||||
|
|
@ -239,7 +239,7 @@ order by %s""" % (lim, sort)
|
|||
# gather nids
|
||||
regex = value.replace("%", ".*")
|
||||
nids = []
|
||||
for (id,mid,flds) in self.deck.db.execute("""
|
||||
for (id,mid,flds) in self.col.db.execute("""
|
||||
select id, mid, flds from notes
|
||||
where mid in %s and flds like ? escape '\\'""" % (
|
||||
ids2str(mods.keys())),
|
||||
|
|
@ -258,7 +258,7 @@ where mid in %s and flds like ? escape '\\'""" % (
|
|||
def _parseQuery(self):
|
||||
tokens = []
|
||||
res = []
|
||||
allowedfields = fieldNames(self.deck)
|
||||
allowedfields = fieldNames(self.col)
|
||||
def addSearchFieldToken(field, value, isNeg):
|
||||
if field.lower() in allowedfields:
|
||||
res.append((field + ':' + value, isNeg, SEARCH_FIELD))
|
||||
|
|
@ -370,11 +370,11 @@ where mid in %s and flds like ? escape '\\'""" % (
|
|||
# Find and replace
|
||||
##########################################################################
|
||||
|
||||
def findReplace(deck, nids, src, dst, regex=False, field=None, fold=True):
|
||||
def findReplace(col, nids, src, dst, regex=False, field=None, fold=True):
|
||||
"Find and replace fields in a note."
|
||||
mmap = {}
|
||||
if field:
|
||||
for m in deck.models.all():
|
||||
for m in col.models.all():
|
||||
for f in m['flds']:
|
||||
if f['name'] == field:
|
||||
mmap[m['id']] = f['ord']
|
||||
|
|
@ -389,7 +389,7 @@ def findReplace(deck, nids, src, dst, regex=False, field=None, fold=True):
|
|||
def repl(str):
|
||||
return re.sub(regex, dst, str)
|
||||
d = []
|
||||
for nid, mid, flds in deck.db.execute(
|
||||
for nid, mid, flds in col.db.execute(
|
||||
"select id, mid, flds from notes where id in "+ids2str(nids)):
|
||||
origFlds = flds
|
||||
# does it match?
|
||||
|
|
@ -402,19 +402,19 @@ def findReplace(deck, nids, src, dst, regex=False, field=None, fold=True):
|
|||
sflds[c] = repl(sflds[c])
|
||||
flds = joinFields(sflds)
|
||||
if flds != origFlds:
|
||||
d.append(dict(nid=nid,flds=flds,u=deck.usn(),m=intTime()))
|
||||
d.append(dict(nid=nid,flds=flds,u=col.usn(),m=intTime()))
|
||||
if not d:
|
||||
return 0
|
||||
# replace
|
||||
deck.db.executemany("update notes set flds=:flds,mod=:m,usn=:u where id=:nid", d)
|
||||
deck.updateFieldCache(nids)
|
||||
col.db.executemany("update notes set flds=:flds,mod=:m,usn=:u where id=:nid", d)
|
||||
col.updateFieldCache(nids)
|
||||
return len(d)
|
||||
|
||||
# Find duplicates
|
||||
##########################################################################
|
||||
|
||||
def findDuplicates(deck, fmids):
|
||||
data = deck.db.all(
|
||||
def findDuplicates(col, fmids):
|
||||
data = col.db.all(
|
||||
"select nid, value from fdata where fmid in %s" %
|
||||
ids2str(fmids))
|
||||
vals = {}
|
||||
|
|
|
|||
|
|
@ -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)."
|
||||
|
|
|
|||
|
|
@ -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),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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):
|
||||
|
|
|
|||
|
|
@ -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))
|
||||
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import re, unicodedata, time
|
|||
#import chardet
|
||||
|
||||
|
||||
from anki import Deck
|
||||
#from anki import Deck
|
||||
|
||||
class SmartDict(dict):
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
|
|
|
|||
154
anki/sched.py
154
anki/sched.py
|
|
@ -18,8 +18,8 @@ from anki.hooks import runHook
|
|||
# the standard Anki scheduler
|
||||
class Scheduler(object):
|
||||
name = "std"
|
||||
def __init__(self, deck):
|
||||
self.deck = deck
|
||||
def __init__(self, col):
|
||||
self.col = col
|
||||
self.queueLimit = 50
|
||||
self.reportLimit = 1000
|
||||
# fixme: replace reps with group based counts
|
||||
|
|
@ -31,7 +31,7 @@ class Scheduler(object):
|
|||
self._checkDay()
|
||||
id = self._getCardId()
|
||||
if id:
|
||||
c = self.deck.getCard(id)
|
||||
c = self.col.getCard(id)
|
||||
c.startTimer()
|
||||
return c
|
||||
|
||||
|
|
@ -43,7 +43,7 @@ class Scheduler(object):
|
|||
|
||||
def answerCard(self, card, ease):
|
||||
assert ease >= 1 and ease <= 4
|
||||
self.deck.markReview(card)
|
||||
self.col.markReview(card)
|
||||
self.reps += 1
|
||||
card.reps += 1
|
||||
wasNew = (card.queue == 0) and card.type != 2
|
||||
|
|
@ -65,7 +65,7 @@ class Scheduler(object):
|
|||
raise Exception("Invalid queue")
|
||||
self._updateStats(card, 'time', card.timeTaken())
|
||||
card.mod = intTime()
|
||||
card.usn = self.deck.usn()
|
||||
card.usn = self.col.usn()
|
||||
card.flushSched()
|
||||
|
||||
def repCounts(self):
|
||||
|
|
@ -76,7 +76,7 @@ class Scheduler(object):
|
|||
|
||||
def dueForecast(self, days=7):
|
||||
"Return counts over next DAYS. Includes today."
|
||||
daysd = dict(self.deck.db.all("""
|
||||
daysd = dict(self.col.db.all("""
|
||||
select due, count() from cards
|
||||
where gid in %s and queue = 2
|
||||
and due between ? and ?
|
||||
|
|
@ -103,7 +103,7 @@ order by due""" % self._groupLimit(),
|
|||
|
||||
def onClose(self):
|
||||
"Unbury and remove temporary suspends on close."
|
||||
self.deck.db.execute(
|
||||
self.col.db.execute(
|
||||
"update cards set queue = type where queue between -3 and -2")
|
||||
|
||||
# Rev/lrn/time daily stats
|
||||
|
|
@ -111,11 +111,11 @@ order by due""" % self._groupLimit(),
|
|||
|
||||
def _updateStats(self, card, type, cnt=1):
|
||||
key = type+"Today"
|
||||
for g in ([self.deck.groups.get(card.gid)] +
|
||||
self.deck.groups.parents(card.gid)):
|
||||
for g in ([self.col.groups.get(card.gid)] +
|
||||
self.col.groups.parents(card.gid)):
|
||||
# add
|
||||
g[key][1] += cnt
|
||||
self.deck.groups.save(g)
|
||||
self.col.groups.save(g)
|
||||
|
||||
# Group counts
|
||||
##########################################################################
|
||||
|
|
@ -124,12 +124,12 @@ order by due""" % self._groupLimit(),
|
|||
"Returns [groupname, gid, hasDue, hasNew]"
|
||||
# find groups with 1 or more due cards
|
||||
gids = {}
|
||||
for g in self.deck.groups.all():
|
||||
for g in self.col.groups.all():
|
||||
hasDue = self._groupHasLrn(g['id']) or self._groupHasRev(g['id'])
|
||||
hasNew = self._groupHasNew(g['id'])
|
||||
gids[g['id']] = [hasDue or 0, hasNew or 0]
|
||||
return [[grp['name'], int(gid)]+gids[int(gid)] #.get(int(gid))
|
||||
for (gid, grp) in self.deck.groups.groups.items()]
|
||||
for (gid, grp) in self.col.groups.groups.items()]
|
||||
|
||||
def groupCountTree(self):
|
||||
return self._groupChildren(self.groupCounts())
|
||||
|
|
@ -138,7 +138,7 @@ order by due""" % self._groupLimit(),
|
|||
"Like the count tree without the counts. Faster."
|
||||
return self._groupChildren(
|
||||
[[grp['name'], int(gid), 0, 0, 0]
|
||||
for (gid, grp) in self.deck.groups.groups.items()])
|
||||
for (gid, grp) in self.col.groups.groups.items()])
|
||||
|
||||
def _groupChildren(self, grps):
|
||||
# first, split the group names into components
|
||||
|
|
@ -208,13 +208,13 @@ order by due""" % self._groupLimit(),
|
|||
self.newCount = 0
|
||||
pcounts = {}
|
||||
# for each of the active groups
|
||||
for gid in self.deck.groups.active():
|
||||
for gid in self.col.groups.active():
|
||||
# get the individual group's limit
|
||||
lim = self._groupNewLimitSingle(self.deck.groups.get(gid))
|
||||
lim = self._groupNewLimitSingle(self.col.groups.get(gid))
|
||||
if not lim:
|
||||
continue
|
||||
# check the parents
|
||||
parents = self.deck.groups.parents(gid)
|
||||
parents = self.col.groups.parents(gid)
|
||||
for p in parents:
|
||||
# add if missing
|
||||
if p['id'] not in pcounts:
|
||||
|
|
@ -222,7 +222,7 @@ order by due""" % self._groupLimit(),
|
|||
# take minimum of child and parent
|
||||
lim = min(pcounts[p['id']], lim)
|
||||
# see how many cards we actually have
|
||||
cnt = self.deck.db.scalar("""
|
||||
cnt = self.col.db.scalar("""
|
||||
select count() from (select 1 from cards where
|
||||
gid = ? and queue = 0 limit ?)""", gid, lim)
|
||||
# if non-zero, decrement from parent counts
|
||||
|
|
@ -235,7 +235,7 @@ gid = ? and queue = 0 limit ?)""", gid, lim)
|
|||
|
||||
def _resetNew(self):
|
||||
self._resetNewCount()
|
||||
self.newGids = self.deck.groups.active()
|
||||
self.newGids = self.col.groups.active()
|
||||
self._newQueue = []
|
||||
self._updateNewCardRatio()
|
||||
|
||||
|
|
@ -249,7 +249,7 @@ gid = ? and queue = 0 limit ?)""", gid, lim)
|
|||
lim = min(self.queueLimit, self._groupNewLimit(gid))
|
||||
if lim:
|
||||
# fill the queue with the current gid
|
||||
self._newQueue = self.deck.db.all("""
|
||||
self._newQueue = self.col.db.all("""
|
||||
select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
|
||||
if self._newQueue:
|
||||
self._newQueue.reverse()
|
||||
|
|
@ -262,7 +262,7 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
|
|||
return
|
||||
(id, due) = self._newQueue.pop()
|
||||
# move any siblings to the end?
|
||||
conf = self.deck.groups.conf(self.newGids[0])
|
||||
conf = self.col.groups.conf(self.newGids[0])
|
||||
if conf['new']['order'] == NEW_TODAY_ORD:
|
||||
n = len(self._newQueue)
|
||||
while self._newQueue and self._newQueue[-1][1] == due:
|
||||
|
|
@ -275,7 +275,7 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
|
|||
return id
|
||||
|
||||
def _updateNewCardRatio(self):
|
||||
if self.deck.groups.top()['newSpread'] == NEW_CARDS_DISTRIBUTE:
|
||||
if self.col.groups.top()['newSpread'] == NEW_CARDS_DISTRIBUTE:
|
||||
if self.newCount:
|
||||
self.newCardModulus = (
|
||||
(self.newCount + self.revCount) / self.newCount)
|
||||
|
|
@ -289,9 +289,9 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
|
|||
"True if it's time to display a new card when distributing."
|
||||
if not self.newCount:
|
||||
return False
|
||||
if self.deck.groups.top()['newSpread'] == NEW_CARDS_LAST:
|
||||
if self.col.groups.top()['newSpread'] == NEW_CARDS_LAST:
|
||||
return False
|
||||
elif self.deck.groups.top()['newSpread'] == NEW_CARDS_FIRST:
|
||||
elif self.col.groups.top()['newSpread'] == NEW_CARDS_FIRST:
|
||||
return True
|
||||
elif self.newCardModulus:
|
||||
return self.reps and self.reps % self.newCardModulus == 0
|
||||
|
|
@ -299,14 +299,14 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
|
|||
def _groupHasNew(self, gid):
|
||||
if not self._groupNewLimit(gid):
|
||||
return False
|
||||
return self.deck.db.scalar(
|
||||
return self.col.db.scalar(
|
||||
"select 1 from cards where gid = ? and queue = 0 limit 1", gid)
|
||||
|
||||
def _groupNewLimit(self, gid):
|
||||
sel = self.deck.groups.get(gid)
|
||||
sel = self.col.groups.get(gid)
|
||||
lim = -1
|
||||
# for the group and each of its parents
|
||||
for g in [sel] + self.deck.groups.parents(gid):
|
||||
for g in [sel] + self.col.groups.parents(gid):
|
||||
rem = self._groupNewLimitSingle(g)
|
||||
if lim == -1:
|
||||
lim = rem
|
||||
|
|
@ -315,14 +315,14 @@ select id, due from cards where gid = ? and queue = 0 limit ?""", gid, lim)
|
|||
return lim
|
||||
|
||||
def _groupNewLimitSingle(self, g):
|
||||
c = self.deck.groups.conf(g['id'])
|
||||
c = self.col.groups.conf(g['id'])
|
||||
return max(0, c['new']['perDay'] - g['newToday'][1])
|
||||
|
||||
# Learning queue
|
||||
##########################################################################
|
||||
|
||||
def _resetLrnCount(self):
|
||||
(self.lrnCount, self.lrnRepCount) = self.deck.db.first("""
|
||||
(self.lrnCount, self.lrnRepCount) = self.col.db.first("""
|
||||
select count(), sum(left) from (select left from cards where
|
||||
gid in %s and queue = 1 and due < ? limit %d)""" % (
|
||||
self._groupLimit(), self.reportLimit),
|
||||
|
|
@ -338,7 +338,7 @@ gid in %s and queue = 1 and due < ? limit %d)""" % (
|
|||
return False
|
||||
if self._lrnQueue:
|
||||
return True
|
||||
self._lrnQueue = self.deck.db.all("""
|
||||
self._lrnQueue = self.col.db.all("""
|
||||
select due, id from cards where
|
||||
gid in %s and queue = 1 and due < :lim
|
||||
limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
|
||||
|
|
@ -350,7 +350,7 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
|
|||
if self._fillLrn():
|
||||
cutoff = time.time()
|
||||
if collapse:
|
||||
cutoff += self.deck.groups.top()['collapseTime']
|
||||
cutoff += self.col.groups.top()['collapseTime']
|
||||
if self._lrnQueue[0][0] < cutoff:
|
||||
id = heappop(self._lrnQueue)[1]
|
||||
self.lrnCount -= 1
|
||||
|
|
@ -436,9 +436,9 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
|
|||
lastIvl = -(self._delayForGrade(conf, lastLeft))
|
||||
ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.left))
|
||||
def log():
|
||||
self.deck.db.execute(
|
||||
self.col.db.execute(
|
||||
"insert into revlog values (?,?,?,?,?,?,?,?,?)",
|
||||
int(time.time()*1000), card.id, self.deck.usn(), ease,
|
||||
int(time.time()*1000), card.id, self.col.usn(), ease,
|
||||
ivl, lastIvl, card.factor, card.timeTaken(), type)
|
||||
try:
|
||||
log()
|
||||
|
|
@ -452,33 +452,33 @@ limit %d""" % (self._groupLimit(), self.reportLimit), lim=self.dayCutoff)
|
|||
extra = ""
|
||||
if ids:
|
||||
extra = " and id in "+ids2str(ids)
|
||||
self.deck.db.execute("""
|
||||
self.col.db.execute("""
|
||||
update cards set
|
||||
due = edue, queue = 2, mod = %d, usn = %d
|
||||
where queue = 1 and type = 2
|
||||
%s
|
||||
""" % (intTime(), self.deck.usn(), extra))
|
||||
""" % (intTime(), self.col.usn(), extra))
|
||||
|
||||
def _groupHasLrn(self, gid):
|
||||
return self.deck.db.scalar(
|
||||
return self.col.db.scalar(
|
||||
"select 1 from cards where gid = ? and queue = 1 "
|
||||
"and due < ? limit 1",
|
||||
gid, intTime() + self.deck.groups.top()['collapseTime'])
|
||||
gid, intTime() + self.col.groups.top()['collapseTime'])
|
||||
|
||||
# Reviews
|
||||
##########################################################################
|
||||
|
||||
def _groupHasRev(self, gid):
|
||||
return self.deck.db.scalar(
|
||||
return self.col.db.scalar(
|
||||
"select 1 from cards where gid = ? and queue = 2 "
|
||||
"and due <= ? limit 1",
|
||||
gid, self.today)
|
||||
|
||||
def _resetRevCount(self):
|
||||
top = self.deck.groups.top()
|
||||
top = self.col.groups.top()
|
||||
lim = min(self.reportLimit,
|
||||
max(0, top['revLim'] - top['revToday'][1]))
|
||||
self.revCount = self.deck.db.scalar("""
|
||||
self.revCount = self.col.db.scalar("""
|
||||
select count() from (select id from cards where
|
||||
gid in %s and queue = 2 and due <= :day limit %d)""" % (
|
||||
self._groupLimit(), lim), day=self.today)
|
||||
|
|
@ -492,12 +492,12 @@ gid in %s and queue = 2 and due <= :day limit %d)""" % (
|
|||
return False
|
||||
if self._revQueue:
|
||||
return True
|
||||
self._revQueue = self.deck.db.list("""
|
||||
self._revQueue = self.col.db.list("""
|
||||
select id from cards where
|
||||
gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
|
||||
self._groupLimit(), self._revOrder(), self.queueLimit),
|
||||
lim=self.today)
|
||||
if not self.deck.conf['revOrder']:
|
||||
if not self.col.conf['revOrder']:
|
||||
r = random.Random()
|
||||
r.seed(self.today)
|
||||
r.shuffle(self._revQueue)
|
||||
|
|
@ -509,8 +509,8 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
|
|||
return self._revQueue.pop()
|
||||
|
||||
def _revOrder(self):
|
||||
if self.deck.conf['revOrder']:
|
||||
return "order by %s" % ("ivl desc", "ivl")[self.deck.conf['revOrder']-1]
|
||||
if self.col.conf['revOrder']:
|
||||
return "order by %s" % ("ivl desc", "ivl")[self.col.conf['revOrder']-1]
|
||||
return ""
|
||||
|
||||
# Answering a review card
|
||||
|
|
@ -555,9 +555,9 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
|
|||
|
||||
def _logRev(self, card, ease):
|
||||
def log():
|
||||
self.deck.db.execute(
|
||||
self.col.db.execute(
|
||||
"insert into revlog values (?,?,?,?,?,?,?,?,?)",
|
||||
int(time.time()*1000), card.id, self.deck.usn(), ease,
|
||||
int(time.time()*1000), card.id, self.col.usn(), ease,
|
||||
card.ivl, card.lastIvl, card.factor, card.timeTaken(),
|
||||
1)
|
||||
try:
|
||||
|
|
@ -604,7 +604,7 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
|
|||
idealDue = self.today + idealIvl
|
||||
conf = self._cardConf(card)['rev']
|
||||
# find sibling positions
|
||||
dues = self.deck.db.list(
|
||||
dues = self.col.db.list(
|
||||
"select due from cards where nid = ? and queue = 2"
|
||||
" and id != ?", card.nid, card.id)
|
||||
if not dues or idealDue not in dues:
|
||||
|
|
@ -653,19 +653,19 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
|
|||
##########################################################################
|
||||
|
||||
def _cardConf(self, card):
|
||||
return self.deck.groups.conf(card.gid)
|
||||
return self.col.groups.conf(card.gid)
|
||||
|
||||
def _groupLimit(self):
|
||||
return ids2str(self.deck.groups.active())
|
||||
return ids2str(self.col.groups.active())
|
||||
|
||||
# Daily cutoff
|
||||
##########################################################################
|
||||
|
||||
def _updateCutoff(self):
|
||||
# days since deck created
|
||||
self.today = int((time.time() - self.deck.crt) / 86400)
|
||||
# days since col created
|
||||
self.today = int((time.time() - self.col.crt) / 86400)
|
||||
# end of day cutoff
|
||||
self.dayCutoff = self.deck.crt + (self.today+1)*86400
|
||||
self.dayCutoff = self.col.crt + (self.today+1)*86400
|
||||
# update all selected groups
|
||||
def update(g):
|
||||
save = False
|
||||
|
|
@ -675,11 +675,11 @@ gid in %s and queue = 2 and due <= :lim %s limit %d""" % (
|
|||
save = True
|
||||
g[key] = [self.today, 0]
|
||||
if save:
|
||||
self.deck.groups.save(g)
|
||||
for gid in self.deck.groups.active():
|
||||
update(self.deck.groups.get(gid))
|
||||
self.col.groups.save(g)
|
||||
for gid in self.col.groups.active():
|
||||
update(self.col.groups.get(gid))
|
||||
# update parents too
|
||||
for grp in self.deck.groups.parents(self.deck.groups.selected()):
|
||||
for grp in self.col.groups.parents(self.col.groups.selected()):
|
||||
update(grp)
|
||||
|
||||
def _checkDay(self):
|
||||
|
|
@ -712,14 +712,14 @@ your short-term review workload will become."""))
|
|||
|
||||
def revDue(self):
|
||||
"True if there are any rev cards due."
|
||||
return self.deck.db.scalar(
|
||||
return self.col.db.scalar(
|
||||
("select 1 from cards where gid in %s and queue = 2 "
|
||||
"and due <= ? limit 1") % self._groupLimit(),
|
||||
self.today)
|
||||
|
||||
def newDue(self):
|
||||
"True if there are any new cards due."
|
||||
return self.deck.db.scalar(
|
||||
return self.col.db.scalar(
|
||||
("select 1 from cards where gid in %s and queue = 0 "
|
||||
"limit 1") % self._groupLimit())
|
||||
|
||||
|
|
@ -771,34 +771,34 @@ your short-term review workload will become."""))
|
|||
def suspendCards(self, ids):
|
||||
"Suspend cards."
|
||||
self.removeFailed(ids)
|
||||
self.deck.db.execute(
|
||||
self.col.db.execute(
|
||||
"update cards set queue=-1,mod=?,usn=? where id in "+
|
||||
ids2str(ids), intTime(), self.deck.usn())
|
||||
ids2str(ids), intTime(), self.col.usn())
|
||||
|
||||
def unsuspendCards(self, ids):
|
||||
"Unsuspend cards."
|
||||
self.deck.db.execute(
|
||||
self.col.db.execute(
|
||||
"update cards set queue=type,mod=?,usn=? "
|
||||
"where queue = -1 and id in "+ ids2str(ids),
|
||||
intTime(), self.deck.usn())
|
||||
intTime(), self.col.usn())
|
||||
|
||||
def buryNote(self, nid):
|
||||
"Bury all cards for note until next session."
|
||||
self.deck.setDirty()
|
||||
self.col.setDirty()
|
||||
self.removeFailed(
|
||||
self.deck.db.list("select id from cards where nid = ?", nid))
|
||||
self.deck.db.execute("update cards set queue = -2 where nid = ?", nid)
|
||||
self.col.db.list("select id from cards where nid = ?", nid))
|
||||
self.col.db.execute("update cards set queue = -2 where nid = ?", nid)
|
||||
|
||||
# Resetting
|
||||
##########################################################################
|
||||
|
||||
def forgetCards(self, ids):
|
||||
"Put cards at the end of the new queue."
|
||||
self.deck.db.execute(
|
||||
self.col.db.execute(
|
||||
"update cards set type=0,queue=0,ivl=0 where id in "+ids2str(ids))
|
||||
pmax = self.deck.db.scalar("select max(due) from cards where type=0")
|
||||
pmax = self.col.db.scalar("select max(due) from cards where type=0")
|
||||
# takes care of mod + usn
|
||||
self.sortCards(ids, start=pmax+1, shuffle=self.deck.models.randomNew())
|
||||
self.sortCards(ids, start=pmax+1, shuffle=self.col.models.randomNew())
|
||||
|
||||
def reschedCards(self, ids, imin, imax):
|
||||
"Put cards in review queue with a new interval in days (min, max)."
|
||||
|
|
@ -808,7 +808,7 @@ your short-term review workload will become."""))
|
|||
for id in ids:
|
||||
r = random.randint(imin, imax)
|
||||
d.append(dict(id=id, due=r+t, ivl=max(1, r), mod=mod))
|
||||
self.deck.db.executemany(
|
||||
self.col.db.executemany(
|
||||
"update cards set type=2,queue=2,ivl=:ivl,due=:due where id=:id",
|
||||
d)
|
||||
|
||||
|
|
@ -818,7 +818,7 @@ your short-term review workload will become."""))
|
|||
def sortCards(self, cids, start=1, step=1, shuffle=False, shift=False):
|
||||
scids = ids2str(cids)
|
||||
now = intTime()
|
||||
nids = self.deck.db.list(
|
||||
nids = self.col.db.list(
|
||||
("select distinct nid from cards where type = 0 and id in %s "
|
||||
"order by nid") % scids)
|
||||
if not nids:
|
||||
|
|
@ -833,27 +833,27 @@ your short-term review workload will become."""))
|
|||
high = start+c*step
|
||||
# shift?
|
||||
if shift:
|
||||
low = self.deck.db.scalar(
|
||||
low = self.col.db.scalar(
|
||||
"select min(due) from cards where due >= ? and type = 0 "
|
||||
"and id not in %s" % scids,
|
||||
start)
|
||||
if low is not None:
|
||||
shiftby = high - low + 1
|
||||
self.deck.db.execute("""
|
||||
self.col.db.execute("""
|
||||
update cards set mod=?, usn=?, due=due+? where id not in %s
|
||||
and due >= ?""" % scids, now, self.deck.usn(), shiftby, low)
|
||||
and due >= ?""" % scids, now, self.col.usn(), shiftby, low)
|
||||
# reorder cards
|
||||
d = []
|
||||
for id, nid in self.deck.db.execute(
|
||||
for id, nid in self.col.db.execute(
|
||||
"select id, nid from cards where type = 0 and id in "+scids):
|
||||
d.append(dict(now=now, due=due[nid], usn=self.deck.usn(), cid=id))
|
||||
self.deck.db.executemany(
|
||||
d.append(dict(now=now, due=due[nid], usn=self.col.usn(), cid=id))
|
||||
self.col.db.executemany(
|
||||
"update cards set due=:due,mod=:now,usn=:usn where id = :cid""", d)
|
||||
|
||||
# fixme: because it's a model property now, these should be done on a
|
||||
# per-model basis
|
||||
def randomizeCards(self):
|
||||
self.sortCards(self.deck.db.list("select id from cards"), shuffle=True)
|
||||
self.sortCards(self.col.db.list("select id from cards"), shuffle=True)
|
||||
|
||||
def orderCards(self):
|
||||
self.sortCards(self.deck.db.list("select id from cards"))
|
||||
self.sortCards(self.col.db.list("select id from cards"))
|
||||
|
|
|
|||
|
|
@ -192,7 +192,7 @@ def stopMplayer(*args):
|
|||
return
|
||||
mplayerManager.kill()
|
||||
|
||||
addHook("deckClosed", stopMplayer)
|
||||
addHook("colClosed", stopMplayer)
|
||||
|
||||
# PyAudio recording
|
||||
##########################################################################
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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}))
|
||||
|
|
|
|||
180
anki/sync.py
180
anki/sync.py
|
|
@ -23,8 +23,8 @@ if simplejson.__version__ < "1.7.3":
|
|||
# todo:
|
||||
# - ensure all urllib references are converted to urllib2 for proxies
|
||||
# - ability to cancel
|
||||
# - need to make sure syncing doesn't bump the deck modified time if nothing was
|
||||
# changed, since by default closing the deck bumps the mod time
|
||||
# - need to make sure syncing doesn't bump the col modified time if nothing was
|
||||
# changed, since by default closing the col bumps the mod time
|
||||
# - ensure the user doesn't add foreign chars to passsword
|
||||
|
||||
# Incremental syncing
|
||||
|
|
@ -34,8 +34,8 @@ from anki.consts import *
|
|||
|
||||
class Syncer(object):
|
||||
|
||||
def __init__(self, deck, server=None):
|
||||
self.deck = deck
|
||||
def __init__(self, col, server=None):
|
||||
self.col = col
|
||||
self.server = server
|
||||
|
||||
def status(self, type):
|
||||
|
|
@ -90,7 +90,7 @@ class Syncer(object):
|
|||
return "success"
|
||||
|
||||
def meta(self):
|
||||
return (self.deck.mod, self.deck.scm, self.deck._usn, intTime(), None)
|
||||
return (self.col.mod, self.col.scm, self.col._usn, intTime(), None)
|
||||
|
||||
def changes(self):
|
||||
"Bundle up deletions and small objects, and apply if server."
|
||||
|
|
@ -104,7 +104,7 @@ class Syncer(object):
|
|||
|
||||
def applyChanges(self, minUsn, lnewer, changes):
|
||||
# we're the server; save info
|
||||
self.maxUsn = self.deck._usn
|
||||
self.maxUsn = self.col._usn
|
||||
self.minUsn = minUsn
|
||||
self.lnewer = not lnewer
|
||||
self.rchg = changes
|
||||
|
|
@ -127,33 +127,33 @@ class Syncer(object):
|
|||
def sanityCheck(self):
|
||||
# some basic checks to ensure the sync went ok. this is slow, so will
|
||||
# be removed before official release
|
||||
assert not self.deck.db.scalar("""
|
||||
assert not self.col.db.scalar("""
|
||||
select count() from cards where nid not in (select id from notes)""")
|
||||
assert not self.deck.db.scalar("""
|
||||
assert not self.col.db.scalar("""
|
||||
select count() from notes where id not in (select distinct nid from cards)""")
|
||||
for t in "cards", "notes", "revlog", "graves":
|
||||
assert not self.deck.db.scalar(
|
||||
assert not self.col.db.scalar(
|
||||
"select count() from %s where usn = -1" % t)
|
||||
for g in self.deck.groups.all():
|
||||
for g in self.col.groups.all():
|
||||
assert g['usn'] != -1
|
||||
for t, usn in self.deck.tags.allItems():
|
||||
for t, usn in self.col.tags.allItems():
|
||||
assert usn != -1
|
||||
for m in self.deck.models.all():
|
||||
for m in self.col.models.all():
|
||||
assert m['usn'] != -1
|
||||
return [
|
||||
self.deck.db.scalar("select count() from cards"),
|
||||
self.deck.db.scalar("select count() from notes"),
|
||||
self.deck.db.scalar("select count() from revlog"),
|
||||
self.deck.db.scalar("select count() from nsums"),
|
||||
self.deck.db.scalar("select count() from graves"),
|
||||
len(self.deck.models.all()),
|
||||
len(self.deck.tags.all()),
|
||||
len(self.deck.groups.all()),
|
||||
len(self.deck.groups.allConf()),
|
||||
self.col.db.scalar("select count() from cards"),
|
||||
self.col.db.scalar("select count() from notes"),
|
||||
self.col.db.scalar("select count() from revlog"),
|
||||
self.col.db.scalar("select count() from nsums"),
|
||||
self.col.db.scalar("select count() from graves"),
|
||||
len(self.col.models.all()),
|
||||
len(self.col.tags.all()),
|
||||
len(self.col.groups.all()),
|
||||
len(self.col.groups.allConf()),
|
||||
]
|
||||
|
||||
def usnLim(self):
|
||||
if self.deck.server:
|
||||
if self.col.server:
|
||||
return "usn >= %d" % self.minUsn
|
||||
else:
|
||||
return "usn = -1"
|
||||
|
|
@ -162,9 +162,9 @@ select count() from notes where id not in (select distinct nid from cards)""")
|
|||
if not mod:
|
||||
# server side; we decide new mod time
|
||||
mod = intTime(1000)
|
||||
self.deck.ls = mod
|
||||
self.deck._usn = self.maxUsn + 1
|
||||
self.deck.save(mod=mod)
|
||||
self.col.ls = mod
|
||||
self.col._usn = self.maxUsn + 1
|
||||
self.col.save(mod=mod)
|
||||
return mod
|
||||
|
||||
# Chunked syncing
|
||||
|
|
@ -176,7 +176,7 @@ select count() from notes where id not in (select distinct nid from cards)""")
|
|||
|
||||
def cursorForTable(self, table):
|
||||
lim = self.usnLim()
|
||||
x = self.deck.db.execute
|
||||
x = self.col.db.execute
|
||||
d = (self.maxUsn, lim)
|
||||
if table == "revlog":
|
||||
return x("""
|
||||
|
|
@ -206,8 +206,8 @@ from notes where %s""" % d)
|
|||
self.tablesLeft.pop(0)
|
||||
self.cursor = None
|
||||
# if we're the client, mark the objects as having been sent
|
||||
if not self.deck.server:
|
||||
self.deck.db.execute(
|
||||
if not self.col.server:
|
||||
self.col.db.execute(
|
||||
"update %s set usn=? where usn=-1"%curTable,
|
||||
self.maxUsn)
|
||||
buf[curTable] = rows
|
||||
|
|
@ -231,11 +231,11 @@ from notes where %s""" % d)
|
|||
cards = []
|
||||
notes = []
|
||||
groups = []
|
||||
if self.deck.server:
|
||||
curs = self.deck.db.execute(
|
||||
if self.col.server:
|
||||
curs = self.col.db.execute(
|
||||
"select oid, type from graves where usn >= ?", self.minUsn)
|
||||
else:
|
||||
curs = self.deck.db.execute(
|
||||
curs = self.col.db.execute(
|
||||
"select oid, type from graves where usn = -1")
|
||||
for oid, type in curs:
|
||||
if type == REM_CARD:
|
||||
|
|
@ -244,100 +244,100 @@ from notes where %s""" % d)
|
|||
notes.append(oid)
|
||||
else:
|
||||
groups.append(oid)
|
||||
if not self.deck.server:
|
||||
self.deck.db.execute("update graves set usn=? where usn=-1",
|
||||
if not self.col.server:
|
||||
self.col.db.execute("update graves set usn=? where usn=-1",
|
||||
self.maxUsn)
|
||||
return dict(cards=cards, notes=notes, groups=groups)
|
||||
|
||||
def mergeGraves(self, graves):
|
||||
# notes first, so we don't end up with duplicate graves
|
||||
self.deck._remNotes(graves['notes'])
|
||||
self.deck.remCards(graves['cards'])
|
||||
self.col._remNotes(graves['notes'])
|
||||
self.col.remCards(graves['cards'])
|
||||
for oid in graves['groups']:
|
||||
self.deck.groups.rem(oid)
|
||||
self.col.groups.rem(oid)
|
||||
|
||||
# Models
|
||||
##########################################################################
|
||||
|
||||
def getModels(self):
|
||||
if self.deck.server:
|
||||
return [m for m in self.deck.models.all() if m['usn'] >= self.minUsn]
|
||||
if self.col.server:
|
||||
return [m for m in self.col.models.all() if m['usn'] >= self.minUsn]
|
||||
else:
|
||||
mods = [m for m in self.deck.models.all() if m['usn'] == -1]
|
||||
mods = [m for m in self.col.models.all() if m['usn'] == -1]
|
||||
for m in mods:
|
||||
m['usn'] = self.maxUsn
|
||||
self.deck.models.save()
|
||||
self.col.models.save()
|
||||
return mods
|
||||
|
||||
def mergeModels(self, rchg):
|
||||
for r in rchg:
|
||||
l = self.deck.models.get(r['id'])
|
||||
l = self.col.models.get(r['id'])
|
||||
# if missing locally or server is newer, update
|
||||
if not l or r['mod'] > l['mod']:
|
||||
self.deck.models.update(r)
|
||||
self.col.models.update(r)
|
||||
|
||||
# Groups
|
||||
##########################################################################
|
||||
|
||||
def getGroups(self):
|
||||
if self.deck.server:
|
||||
if self.col.server:
|
||||
return [
|
||||
[g for g in self.deck.groups.all() if g['usn'] >= self.minUsn],
|
||||
[g for g in self.deck.groups.allConf() if g['usn'] >= self.minUsn]
|
||||
[g for g in self.col.groups.all() if g['usn'] >= self.minUsn],
|
||||
[g for g in self.col.groups.allConf() if g['usn'] >= self.minUsn]
|
||||
]
|
||||
else:
|
||||
groups = [g for g in self.deck.groups.all() if g['usn'] == -1]
|
||||
groups = [g for g in self.col.groups.all() if g['usn'] == -1]
|
||||
for g in groups:
|
||||
g['usn'] = self.maxUsn
|
||||
gconf = [g for g in self.deck.groups.allConf() if g['usn'] == -1]
|
||||
gconf = [g for g in self.col.groups.allConf() if g['usn'] == -1]
|
||||
for g in gconf:
|
||||
g['usn'] = self.maxUsn
|
||||
self.deck.groups.save()
|
||||
self.col.groups.save()
|
||||
return [groups, gconf]
|
||||
|
||||
def mergeGroups(self, rchg):
|
||||
for r in rchg[0]:
|
||||
l = self.deck.groups.get(r['id'], False)
|
||||
l = self.col.groups.get(r['id'], False)
|
||||
# if missing locally or server is newer, update
|
||||
if not l or r['mod'] > l['mod']:
|
||||
self.deck.groups.update(r)
|
||||
self.col.groups.update(r)
|
||||
for r in rchg[1]:
|
||||
l = self.deck.groups.conf(r['id'])
|
||||
l = self.col.groups.conf(r['id'])
|
||||
# if missing locally or server is newer, update
|
||||
if not l or r['mod'] > l['mod']:
|
||||
self.deck.groups.updateConf(r)
|
||||
self.col.groups.updateConf(r)
|
||||
|
||||
# Tags
|
||||
##########################################################################
|
||||
|
||||
def getTags(self):
|
||||
if self.deck.server:
|
||||
return [t for t, usn in self.deck.tags.allItems()
|
||||
if self.col.server:
|
||||
return [t for t, usn in self.col.tags.allItems()
|
||||
if usn >= self.minUsn]
|
||||
else:
|
||||
tags = []
|
||||
for t, usn in self.deck.tags.allItems():
|
||||
for t, usn in self.col.tags.allItems():
|
||||
if usn == -1:
|
||||
self.deck.tags.tags[t] = self.maxUsn
|
||||
self.col.tags.tags[t] = self.maxUsn
|
||||
tags.append(t)
|
||||
self.deck.tags.save()
|
||||
self.col.tags.save()
|
||||
return tags
|
||||
|
||||
def mergeTags(self, tags):
|
||||
self.deck.tags.register(tags, usn=self.maxUsn)
|
||||
self.col.tags.register(tags, usn=self.maxUsn)
|
||||
|
||||
# Cards/notes/revlog
|
||||
##########################################################################
|
||||
|
||||
def mergeRevlog(self, logs):
|
||||
self.deck.db.executemany(
|
||||
self.col.db.executemany(
|
||||
"insert or ignore into revlog values (?,?,?,?,?,?,?,?,?)",
|
||||
logs)
|
||||
|
||||
def newerRows(self, data, table, modIdx):
|
||||
ids = (r[0] for r in data)
|
||||
lmods = {}
|
||||
for id, mod in self.deck.db.execute(
|
||||
for id, mod in self.col.db.execute(
|
||||
"select id, mod from %s where id in %s and %s" % (
|
||||
table, ids2str(ids), self.usnLim())):
|
||||
lmods[id] = mod
|
||||
|
|
@ -348,26 +348,26 @@ from notes where %s""" % d)
|
|||
return update
|
||||
|
||||
def mergeCards(self, cards):
|
||||
self.deck.db.executemany(
|
||||
self.col.db.executemany(
|
||||
"insert or replace into cards values "
|
||||
"(?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)",
|
||||
self.newerRows(cards, "cards", 4))
|
||||
|
||||
def mergeNotes(self, notes):
|
||||
rows = self.newerRows(notes, "notes", 4)
|
||||
self.deck.db.executemany(
|
||||
self.col.db.executemany(
|
||||
"insert or replace into notes values (?,?,?,?,?,?,?,?,?,?,?)",
|
||||
rows)
|
||||
self.deck.updateFieldCache([f[0] for f in rows])
|
||||
self.col.updateFieldCache([f[0] for f in rows])
|
||||
|
||||
# Deck config
|
||||
# Col config
|
||||
##########################################################################
|
||||
|
||||
def getConf(self):
|
||||
return self.deck.conf
|
||||
return self.col.conf
|
||||
|
||||
def mergeConf(self, conf):
|
||||
self.deck.conf = conf
|
||||
self.col.conf = conf
|
||||
|
||||
# Local syncing for unit tests
|
||||
##########################################################################
|
||||
|
|
@ -375,7 +375,7 @@ from notes where %s""" % d)
|
|||
class LocalServer(Syncer):
|
||||
|
||||
# serialize/deserialize payload, so we don't end up sharing objects
|
||||
# between decks
|
||||
# between cols
|
||||
def applyChanges(self, minUsn, lnewer, changes):
|
||||
l = simplejson.loads; d = simplejson.dumps
|
||||
return l(d(Syncer.applyChanges(self, minUsn, lnewer, l(d(changes)))))
|
||||
|
|
@ -499,30 +499,30 @@ class RemoteServer(Syncer, HttpSyncer):
|
|||
|
||||
class FullSyncer(HttpSyncer):
|
||||
|
||||
def __init__(self, deck, hkey):
|
||||
self.deck = deck
|
||||
def __init__(self, col, hkey):
|
||||
self.col = col
|
||||
self.hkey = hkey
|
||||
|
||||
def _con(self):
|
||||
return httplib2.Http(timeout=60)
|
||||
|
||||
def download(self):
|
||||
self.deck.close()
|
||||
self.col.close()
|
||||
resp, cont = self._con().request(
|
||||
SYNC_URL+"download?" + urllib.urlencode(self._vars()))
|
||||
if resp['status'] != '200':
|
||||
raise Exception("Invalid response code: %s" % resp['status'])
|
||||
tpath = self.deck.path + ".tmp"
|
||||
tpath = self.col.path + ".tmp"
|
||||
open(tpath, "wb").write(cont)
|
||||
os.unlink(self.deck.path)
|
||||
os.rename(tpath, self.deck.path)
|
||||
d = DB(self.deck.path)
|
||||
os.unlink(self.col.path)
|
||||
os.rename(tpath, self.col.path)
|
||||
d = DB(self.col.path)
|
||||
assert d.scalar("pragma integrity_check") == "ok"
|
||||
self.deck = None
|
||||
self.col = None
|
||||
|
||||
def upload(self):
|
||||
self.deck.beforeUpload()
|
||||
assert self.postData(self._con(), "upload", open(self.deck.path, "rb"),
|
||||
self.col.beforeUpload()
|
||||
assert self.postData(self._con(), "upload", open(self.col.path, "rb"),
|
||||
self._vars(), comp=6) == "OK"
|
||||
|
||||
# Media syncing
|
||||
|
|
@ -530,16 +530,16 @@ class FullSyncer(HttpSyncer):
|
|||
|
||||
class MediaSyncer(object):
|
||||
|
||||
def __init__(self, deck, server=None):
|
||||
self.deck = deck
|
||||
def __init__(self, col, server=None):
|
||||
self.col = col
|
||||
self.server = server
|
||||
self.added = None
|
||||
|
||||
def sync(self, mediaUsn):
|
||||
# step 1: check if there have been any changes
|
||||
self.deck.media.findChanges()
|
||||
lusn = self.deck.media.usn()
|
||||
if lusn == mediaUsn and not self.deck.media.hasChanged():
|
||||
self.col.media.findChanges()
|
||||
lusn = self.col.media.usn()
|
||||
if lusn == mediaUsn and not self.col.media.hasChanged():
|
||||
return "noChanges"
|
||||
# step 2: send/recv deletions
|
||||
runHook("mediaSync", "remove")
|
||||
|
|
@ -563,30 +563,30 @@ class MediaSyncer(object):
|
|||
# when server has run out of files, it returns bumped usn
|
||||
break
|
||||
# step 5: finalize
|
||||
self.deck.media.setUsn(usn)
|
||||
self.deck.media.clearLog()
|
||||
self.col.media.setUsn(usn)
|
||||
self.col.media.clearLog()
|
||||
# clear cursor so successive calls work
|
||||
self.added = None
|
||||
return "success"
|
||||
|
||||
def removed(self):
|
||||
return self.deck.media.removed()
|
||||
return self.col.media.removed()
|
||||
|
||||
def remove(self, fnames, minUsn=None):
|
||||
self.deck.media.syncRemove(fnames)
|
||||
self.col.media.syncRemove(fnames)
|
||||
if minUsn is not None:
|
||||
# we're the server
|
||||
self.minUsn = minUsn
|
||||
return self.deck.media.removed()
|
||||
return self.col.media.removed()
|
||||
|
||||
def files(self):
|
||||
if not self.added:
|
||||
self.added = self.deck.media.added()
|
||||
return self.deck.media.zipFromAdded(self.added)
|
||||
self.added = self.col.media.added()
|
||||
return self.col.media.zipFromAdded(self.added)
|
||||
|
||||
def addFiles(self, zip):
|
||||
"True if zip is the last in set. Server returns new usn instead."
|
||||
return self.deck.media.syncAdd(zip)
|
||||
return self.col.media.syncAdd(zip)
|
||||
|
||||
# Remote media syncing
|
||||
##########################################################################
|
||||
|
|
|
|||
22
anki/tags.py
22
anki/tags.py
|
|
@ -18,8 +18,8 @@ class TagManager(object):
|
|||
# Registry save/load
|
||||
#############################################################
|
||||
|
||||
def __init__(self, deck):
|
||||
self.deck = deck
|
||||
def __init__(self, col):
|
||||
self.col = col
|
||||
|
||||
def load(self, json):
|
||||
self.tags = simplejson.loads(json)
|
||||
|
|
@ -27,7 +27,7 @@ class TagManager(object):
|
|||
|
||||
def flush(self):
|
||||
if self.changed:
|
||||
self.deck.db.execute("update deck set tags=?",
|
||||
self.col.db.execute("update col set tags=?",
|
||||
simplejson.dumps(self.tags))
|
||||
|
||||
# Registering and fetching tags
|
||||
|
|
@ -39,7 +39,7 @@ class TagManager(object):
|
|||
# versions of the same tag if they ignore the qt autocomplete.
|
||||
for t in tags:
|
||||
if t not in self.tags:
|
||||
self.tags[t] = self.deck.usn() if usn is None else usn
|
||||
self.tags[t] = self.col.usn() if usn is None else usn
|
||||
self.changed = True
|
||||
|
||||
def all(self):
|
||||
|
|
@ -55,7 +55,7 @@ class TagManager(object):
|
|||
self.tags = {}
|
||||
self.changed = True
|
||||
self.register(set(self.split(
|
||||
" ".join(self.deck.db.list("select distinct tags from notes"+lim)))))
|
||||
" ".join(self.col.db.list("select distinct tags from notes"+lim)))))
|
||||
|
||||
def allItems(self):
|
||||
return self.tags.items()
|
||||
|
|
@ -82,7 +82,7 @@ class TagManager(object):
|
|||
fn = self.remFromStr
|
||||
lim = " or ".join(
|
||||
[l+"like :_%d" % c for c, t in enumerate(newTags)])
|
||||
res = self.deck.db.all(
|
||||
res = self.col.db.all(
|
||||
"select id, tags from notes where id in %s and %s" % (
|
||||
ids2str(ids), lim),
|
||||
**dict([("_%d" % x, '%% %s %%' % y)
|
||||
|
|
@ -92,8 +92,8 @@ class TagManager(object):
|
|||
def fix(row):
|
||||
nids.append(row[0])
|
||||
return {'id': row[0], 't': fn(tags, row[1]), 'n':intTime(),
|
||||
'u':self.deck.usn()}
|
||||
self.deck.db.executemany(
|
||||
'u':self.col.usn()}
|
||||
self.col.db.executemany(
|
||||
"update notes set tags=:t,mod=:n,usn=:u where id = :id",
|
||||
[fix(row) for row in res])
|
||||
|
||||
|
|
@ -169,10 +169,10 @@ class TagManager(object):
|
|||
lim = lim2
|
||||
args += ['%% %s %%' % t for t in no]
|
||||
query += " where " + lim
|
||||
return self.deck.db.list(query, *args)
|
||||
return self.col.db.list(query, *args)
|
||||
|
||||
def setGroupForTags(self, yes, no, gid):
|
||||
nids = self.selTagNids(yes, no)
|
||||
self.deck.db.execute(
|
||||
self.col.db.execute(
|
||||
"update cards set gid=?,mod=?,usn=? where nid in "+ids2str(nids),
|
||||
gid, intTime(), self.deck.usn())
|
||||
gid, intTime(), self.col.usn())
|
||||
|
|
|
|||
122
anki/upgrade.py
122
anki/upgrade.py
|
|
@ -6,9 +6,9 @@ import os, time, simplejson, re, datetime, shutil
|
|||
from anki.lang import _
|
||||
from anki.utils import intTime, tmpfile, ids2str, splitFields
|
||||
from anki.db import DB
|
||||
from anki.deck import _Deck
|
||||
from anki.collection import _Collection
|
||||
from anki.consts import *
|
||||
from anki.storage import _addSchema, _getDeckVars, _addDeckVars, \
|
||||
from anki.storage import _addSchema, _getColVars, _addColVars, \
|
||||
_updateIndices
|
||||
|
||||
#
|
||||
|
|
@ -31,9 +31,9 @@ class Upgrader(object):
|
|||
self.path = path
|
||||
self._openDB(path)
|
||||
self._upgradeSchema()
|
||||
self._openDeck()
|
||||
self._upgradeDeck()
|
||||
return self.deck
|
||||
self._openCol()
|
||||
self._upgradeRest()
|
||||
return self.col
|
||||
|
||||
# Integrity checking
|
||||
######################################################################
|
||||
|
|
@ -117,8 +117,8 @@ analyze;""")
|
|||
shutil.copy(path, self.tmppath)
|
||||
self.db = DB(self.tmppath)
|
||||
|
||||
def _openDeck(self):
|
||||
self.deck = _Deck(self.db)
|
||||
def _openCol(self):
|
||||
self.col = _Collection(self.db)
|
||||
|
||||
# Schema upgrade
|
||||
######################################################################
|
||||
|
|
@ -270,7 +270,7 @@ yesCount from reviewHistory"""):
|
|||
tags = {}
|
||||
for t in db.list("select tag from tags"):
|
||||
tags[t] = intTime()
|
||||
db.execute("update deck set tags = ?", simplejson.dumps(tags))
|
||||
db.execute("update col set tags = ?", simplejson.dumps(tags))
|
||||
db.execute("drop table tags")
|
||||
db.execute("drop table cardTags")
|
||||
|
||||
|
|
@ -282,15 +282,14 @@ yesCount from reviewHistory"""):
|
|||
_updateIndices(db)
|
||||
|
||||
def _migrateDeckTbl(self):
|
||||
import anki.deck
|
||||
db = self.db
|
||||
db.execute("delete from deck")
|
||||
db.execute("delete from col")
|
||||
db.execute("""
|
||||
insert or replace into deck select id, cast(created as int), :t,
|
||||
insert or replace into col select id, cast(created as int), :t,
|
||||
:t, 99, 0, 0, cast(lastSync as int),
|
||||
"", "", "", "", "" from decks""", t=intTime())
|
||||
# prepare a group to store the old deck options
|
||||
g, gc, conf = _getDeckVars(db)
|
||||
g, gc, conf = _getColVars(db)
|
||||
# delete old selective study settings, which we can't auto-upgrade easily
|
||||
keys = ("newActive", "newInactive", "revActive", "revInactive")
|
||||
for k in keys:
|
||||
|
|
@ -311,7 +310,7 @@ insert or replace into deck select id, cast(created as int), :t,
|
|||
pass
|
||||
else:
|
||||
conf[k] = v
|
||||
_addDeckVars(db, g, gc, conf)
|
||||
_addColVars(db, g, gc, conf)
|
||||
# clean up
|
||||
db.execute("drop table decks")
|
||||
db.execute("drop table deckVars")
|
||||
|
|
@ -338,7 +337,7 @@ insert or replace into deck select id, cast(created as int), :t,
|
|||
mods[m['id']] = m
|
||||
db.execute("update notes set mid = ? where mid = ?", t, row[0])
|
||||
# save and clean up
|
||||
db.execute("update deck set models = ?", simplejson.dumps(mods))
|
||||
db.execute("update col set models = ?", simplejson.dumps(mods))
|
||||
db.execute("drop table fieldModels")
|
||||
db.execute("drop table cardModels")
|
||||
db.execute("drop table models")
|
||||
|
|
@ -416,7 +415,7 @@ order by ordinal""", mid)):
|
|||
# explicit on upgrade.
|
||||
# - likewise with alignment and background color
|
||||
def _upgradeTemplates(self):
|
||||
d = self.deck
|
||||
d = self.col
|
||||
for m in d.models.all():
|
||||
# cache field styles
|
||||
styles = {}
|
||||
|
|
@ -470,7 +469,7 @@ order by ordinal""", mid)):
|
|||
# process, we automatically convert the references to new fields.
|
||||
|
||||
def _rewriteMediaRefs(self):
|
||||
deck = self.deck
|
||||
col = self.col
|
||||
def rewriteRef(key):
|
||||
all, fname = match
|
||||
if all in state['mflds']:
|
||||
|
|
@ -487,7 +486,7 @@ order by ordinal""", mid)):
|
|||
pre, ofld, suf = m2.groups()
|
||||
# get index of field name
|
||||
try:
|
||||
idx = deck.models.fieldMap(m)[ofld][0]
|
||||
idx = col.models.fieldMap(m)[ofld][0]
|
||||
except:
|
||||
# invalid field or tag reference; don't rewrite
|
||||
return
|
||||
|
|
@ -495,33 +494,33 @@ order by ordinal""", mid)):
|
|||
while 1:
|
||||
state['fields'] += 1
|
||||
fld = "Media %d" % state['fields']
|
||||
if fld not in deck.models.fieldMap(m).keys():
|
||||
if fld not in col.models.fieldMap(m).keys():
|
||||
break
|
||||
# add the new field
|
||||
f = deck.models.newField(fld)
|
||||
deck.models.addField(m, f)
|
||||
f = col.models.newField(fld)
|
||||
col.models.addField(m, f)
|
||||
# loop through notes and write reference into new field
|
||||
data = []
|
||||
for id, flds in self.deck.db.execute(
|
||||
for id, flds in self.col.db.execute(
|
||||
"select id, flds from notes where id in "+
|
||||
ids2str(deck.models.nids(m))):
|
||||
ids2str(col.models.nids(m))):
|
||||
sflds = splitFields(flds)
|
||||
ref = all.replace(fname, pre+sflds[idx]+suf)
|
||||
data.append((flds+ref, id))
|
||||
# update notes
|
||||
deck.db.executemany("update notes set flds=? where id=?",
|
||||
col.db.executemany("update notes set flds=? where id=?",
|
||||
data)
|
||||
# note field for future
|
||||
state['mflds'][fname] = fld
|
||||
new = fld
|
||||
# rewrite reference in template
|
||||
t[key] = t[key].replace(all, "{{{%s}}}" % new)
|
||||
regexps = deck.media.regexps + (
|
||||
regexps = col.media.regexps + (
|
||||
r"(\[latex\](.+?)\[/latex\])",
|
||||
r"(\[\$\](.+?)\[/\$\])",
|
||||
r"(\[\$\$\](.+?)\[/\$\$\])")
|
||||
# process each model
|
||||
for m in deck.models.all():
|
||||
for m in col.models.all():
|
||||
state = dict(mflds={}, fields=0)
|
||||
for t in m['tmpls']:
|
||||
for r in regexps:
|
||||
|
|
@ -530,7 +529,7 @@ order by ordinal""", mid)):
|
|||
for match in re.findall(r, t['afmt']):
|
||||
rewriteRef('afmt')
|
||||
if state['fields']:
|
||||
deck.models.save(m)
|
||||
col.models.save(m)
|
||||
|
||||
# Inactive templates
|
||||
######################################################################
|
||||
|
|
@ -538,7 +537,7 @@ order by ordinal""", mid)):
|
|||
# marked inactive and have no dependent cards.
|
||||
|
||||
def _removeInactive(self):
|
||||
d = self.deck
|
||||
d = self.col
|
||||
for m in d.models.all():
|
||||
remove = []
|
||||
for t in m['tmpls']:
|
||||
|
|
@ -552,15 +551,14 @@ and ord = ? limit 1""", m['id'], t['ord']):
|
|||
m['tmpls'].remove(t)
|
||||
d.models.save(m)
|
||||
|
||||
# Upgrading deck
|
||||
# Post-schema upgrade
|
||||
######################################################################
|
||||
|
||||
def _upgradeDeck(self):
|
||||
def _upgradeRest(self):
|
||||
"Handle the rest of the upgrade to 2.0."
|
||||
import anki.deck
|
||||
deck = self.deck
|
||||
col = self.col
|
||||
# make sure we have a current model id
|
||||
deck.models.setCurrent(deck.models.models.values()[0])
|
||||
col.models.setCurrent(col.models.models.values()[0])
|
||||
# remove unused templates that were marked inactive
|
||||
self._removeInactive()
|
||||
# rewrite media references in card template
|
||||
|
|
@ -568,58 +566,58 @@ and ord = ? limit 1""", m['id'], t['ord']):
|
|||
# template handling has changed
|
||||
self._upgradeTemplates()
|
||||
# set new card order
|
||||
for m in deck.models.all():
|
||||
m['newOrder'] = deck.conf['oldNewOrder']
|
||||
deck.models.save(m)
|
||||
del deck.conf['oldNewOrder']
|
||||
for m in col.models.all():
|
||||
m['newOrder'] = col.conf['oldNewOrder']
|
||||
col.models.save(m)
|
||||
del col.conf['oldNewOrder']
|
||||
# fix creation time
|
||||
deck.sched._updateCutoff()
|
||||
col.sched._updateCutoff()
|
||||
d = datetime.datetime.today()
|
||||
d -= datetime.timedelta(hours=4)
|
||||
d = datetime.datetime(d.year, d.month, d.day)
|
||||
d += datetime.timedelta(hours=4)
|
||||
d -= datetime.timedelta(days=1+int((time.time()-deck.crt)/86400))
|
||||
deck.crt = int(time.mktime(d.timetuple()))
|
||||
deck.sched._updateCutoff()
|
||||
d -= datetime.timedelta(days=1+int((time.time()-col.crt)/86400))
|
||||
col.crt = int(time.mktime(d.timetuple()))
|
||||
col.sched._updateCutoff()
|
||||
# update uniq cache
|
||||
deck.updateFieldCache(deck.db.list("select id from notes"))
|
||||
col.updateFieldCache(col.db.list("select id from notes"))
|
||||
# remove old views
|
||||
for v in ("failedCards", "revCardsOld", "revCardsNew",
|
||||
"revCardsDue", "revCardsRandom", "acqCardsRandom",
|
||||
"acqCardsOld", "acqCardsNew"):
|
||||
deck.db.execute("drop view if exists %s" % v)
|
||||
col.db.execute("drop view if exists %s" % v)
|
||||
# remove stats, as it's all in the revlog now
|
||||
deck.db.execute("drop table if exists stats")
|
||||
col.db.execute("drop table if exists stats")
|
||||
# suspended cards don't use ranges anymore
|
||||
deck.db.execute("update cards set queue=-1 where queue between -3 and -1")
|
||||
deck.db.execute("update cards set queue=-2 where queue between 3 and 5")
|
||||
deck.db.execute("update cards set queue=-3 where queue between 6 and 8")
|
||||
col.db.execute("update cards set queue=-1 where queue between -3 and -1")
|
||||
col.db.execute("update cards set queue=-2 where queue between 3 and 5")
|
||||
col.db.execute("update cards set queue=-3 where queue between 6 and 8")
|
||||
# remove old deleted tables
|
||||
for t in ("cards", "notes", "models", "media"):
|
||||
deck.db.execute("drop table if exists %sDeleted" % t)
|
||||
col.db.execute("drop table if exists %sDeleted" % t)
|
||||
# rewrite due times for new cards
|
||||
deck.db.execute("""
|
||||
col.db.execute("""
|
||||
update cards set due = nid where type=0""")
|
||||
# and failed cards
|
||||
left = len(deck.groups.conf(1)['new']['delays'])
|
||||
deck.db.execute("update cards set edue = ?, left=? where type = 1",
|
||||
deck.sched.today+1, left)
|
||||
left = len(col.groups.conf(1)['new']['delays'])
|
||||
col.db.execute("update cards set edue = ?, left=? where type = 1",
|
||||
col.sched.today+1, left)
|
||||
# and due cards
|
||||
deck.db.execute("""
|
||||
col.db.execute("""
|
||||
update cards set due = cast(
|
||||
(case when due < :stamp then 0 else 1 end) +
|
||||
((due-:stamp)/86400) as int)+:today where type = 2
|
||||
""", stamp=deck.sched.dayCutoff, today=deck.sched.today)
|
||||
""", stamp=col.sched.dayCutoff, today=col.sched.today)
|
||||
# possibly re-randomize
|
||||
if deck.models.randomNew():
|
||||
deck.sched.randomizeCards()
|
||||
if col.models.randomNew():
|
||||
col.sched.randomizeCards()
|
||||
# update insertion id
|
||||
deck.conf['nextPos'] = deck.db.scalar("select max(id) from notes")+1
|
||||
deck.save()
|
||||
col.conf['nextPos'] = col.db.scalar("select max(id) from notes")+1
|
||||
col.save()
|
||||
|
||||
# optimize and finish
|
||||
deck.db.commit()
|
||||
deck.db.execute("vacuum")
|
||||
deck.db.execute("analyze")
|
||||
deck.db.execute("update deck set ver = ?", SCHEMA_VERSION)
|
||||
deck.save()
|
||||
col.db.commit()
|
||||
col.db.execute("vacuum")
|
||||
col.db.execute("analyze")
|
||||
col.db.execute("update col set ver = ?", SCHEMA_VERSION)
|
||||
col.save()
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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():
|
||||
|
|
|
|||
Loading…
Reference in a new issue