diff --git a/anki/cards.py b/anki/cards.py index 860be6126..1b7e50447 100644 --- a/anki/cards.py +++ b/anki/cards.py @@ -110,10 +110,8 @@ streak=?, lapses=?, grade=?, cycles=? where id = ?""", return self._getQA()['a'] def _getQA(self, reload=False): - # this is a hack at the moment if not self._qa or reload: - self._qa = self.deck.updateCache( - [self.id], "card")[0] + self._qa = self.deck.renderQA([self.id], "card")[0] return self._qa def fact(self): diff --git a/anki/deck.py b/anki/deck.py index a6f79c44a..fd6b710c2 100644 --- a/anki/deck.py +++ b/anki/deck.py @@ -2,24 +2,18 @@ # Copyright: Damien Elmes # License: GNU GPL, version 3 or later; http://www.gnu.org/copyleft/gpl.html -import tempfile, time, os, random, sys, re, stat, shutil -import types, traceback, simplejson, datetime -from operator import itemgetter -from itertools import groupby +import time, os, random, re, stat, simplejson from anki.lang import _, ngettext from anki.utils import parseTags, tidyHTML, ids2str, hexifyID, \ - canonifyTags, joinTags, addTags, deleteTags, checksum, fieldChecksum, \ - stripHTML, intTime, splitFields - -from anki.hooks import runHook, hookEmpty, runFilter - + checksum, fieldChecksum, addTags, deleteTags, stripHTML, intTime, \ + splitFields +from anki.hooks import runHook, runFilter from anki.sched import Scheduler from anki.media import MediaRegistry - from anki.consts import * -import anki.latex # sets up hook +import anki.latex # sets up hook import anki.cards, anki.facts, anki.models, anki.template # Settings related to queue building. These may be loaded without the rest of @@ -147,17 +141,9 @@ qconf=?, conf=?, data=?""", "True if schema changed since last sync, or syncing off." return self.schema > self.lastSync - # unsorted + # Object creation helpers ########################################################################## - def nextID(self, type): - id = self.conf.get(type, 1) - self.conf[type] = id+1 - return id - - def reset(self): - self.sched.reset() - def getCard(self, id): return anki.cards.Card(self, id) @@ -168,84 +154,16 @@ qconf=?, conf=?, data=?""", return anki.models.Template(self, self.deck.db.first( "select * from templates where id = ?", id)) - # if card: - # return card - # if sched.name == "main": - # self.stopSession() - # else: - # # in a custom scheduler; return to normal - # print "fixme: this should be done in gui code" - # self.sched.cleanup() - # self.sched = AnkiScheduler(self) - # return self.getCard() + # unsorted + ########################################################################## - def resetCards(self, ids=None): - "Reset progress on cards in IDS." - print "position in resetCards()" - sql = """ -update cards set mod=:now, position=0, type=2, queue=2, lastInterval=0, -interval=0, due=created, factor=2.5, reps=0, successive=0, lapses=0, flags=0""" - sql2 = "delete from revlog" - if ids is None: - lim = "" - else: - sids = ids2str(ids) - sql += " where id in "+sids - sql2 += " where cardId in "+sids - self.db.execute(sql, now=time.time()) - self.db.execute(sql2) - if self.qconf['newCardOrder'] == NEW_CARDS_RANDOM: - # we need to re-randomize now - self.randomizeNewCards(ids) + def nextID(self, type): + id = self.conf.get(type, 1) + self.conf[type] = id+1 + return id - def randomizeNewCards(self, cardIds=None): - "Randomize 'due' on all new cards." - now = time.time() - query = "select distinct fid from cards where reps = 0" - if cardIds: - query += " and id in %s" % ids2str(cardIds) - fids = self.db.list(query) - data = [{'fid': fid, - 'rand': random.uniform(0, now), - 'now': now} for fid in fids] - self.db.executemany(""" -update cards -set due = :rand + ord, -mod = :now -where fid = :fid -and type = 2""", data) - - def orderNewCards(self): - "Set 'due' to card creation time." - self.db.execute(""" -update cards set -due = created, -mod = :now -where type = 2""", now=time.time()) - - def rescheduleCards(self, ids, min, max): - "Reset cards and schedule with new interval in days (min, max)." - self.resetCards(ids) - vals = [] - for id in ids: - r = random.uniform(min*86400, max*86400) - vals.append({ - 'id': id, - 'due': r + time.time(), - 'int': r / 86400.0, - 't': time.time(), - }) - self.db.executemany(""" -update cards set -interval = :int, -due = :due, -reps = 1, -successive = 1, -yesCount = 1, -firstAnswered = :t, -queue = 1, -type = 1, -where id = :id""", vals) + def reset(self): + self.sched.reset() # Times ########################################################################## @@ -508,9 +426,9 @@ due > :now and due < :now""", now=time.time()) # [cid, fid, mid, tid, gid, tags, flds, data] data = [1, 1, fact.model.id, template.id, 1, "", fact.joinedFields(), ""] - now = self._formatQA(fact.model, template, "", data) + now = self._renderQA(fact.model, template, "", data) data[6] = "\x1f".join([""]*len(fact._fields)) - empty = self._formatQA(fact.model, template, "", data) + empty = self._renderQA(fact.model, template, "", data) if now['q'] == empty['q']: continue if not template.conf['allowEmptyAns']: @@ -637,6 +555,74 @@ select id from facts where id not in (select distinct fid from cards)""") delete from facts where id in (select fid from cards where queue = -4); delete from cards where queue = -4;""") + def resetCards(self, ids=None): + "Reset progress on cards in IDS." + print "position in resetCards()" + sql = """ +update cards set mod=:now, position=0, type=2, queue=2, lastInterval=0, +interval=0, due=created, factor=2.5, reps=0, successive=0, lapses=0, flags=0""" + sql2 = "delete from revlog" + if ids is None: + lim = "" + else: + sids = ids2str(ids) + sql += " where id in "+sids + sql2 += " where cardId in "+sids + self.db.execute(sql, now=time.time()) + self.db.execute(sql2) + if self.qconf['newCardOrder'] == NEW_CARDS_RANDOM: + # we need to re-randomize now + self.randomizeNewCards(ids) + + def randomizeNewCards(self, cardIds=None): + "Randomize 'due' on all new cards." + now = time.time() + query = "select distinct fid from cards where reps = 0" + if cardIds: + query += " and id in %s" % ids2str(cardIds) + fids = self.db.list(query) + data = [{'fid': fid, + 'rand': random.uniform(0, now), + 'now': now} for fid in fids] + self.db.executemany(""" +update cards +set due = :rand + ord, +mod = :now +where fid = :fid +and type = 2""", data) + + def orderNewCards(self): + "Set 'due' to card creation time." + self.db.execute(""" +update cards set +due = created, +mod = :now +where type = 2""", now=time.time()) + + def rescheduleCards(self, ids, min, max): + "Reset cards and schedule with new interval in days (min, max)." + self.resetCards(ids) + vals = [] + for id in ids: + r = random.uniform(min*86400, max*86400) + vals.append({ + 'id': id, + 'due': r + time.time(), + 'int': r / 86400.0, + 't': time.time(), + }) + self.db.executemany(""" +update cards set +interval = :int, +due = :due, +reps = 1, +successive = 1, +yesCount = 1, +firstAnswered = :t, +queue = 1, +type = 1, +where id = :id""", vals) + # Models ########################################################################## @@ -743,7 +729,6 @@ update cards set tid = :new, ord = :ord where id in %s""" % ids2str(ids), new=new.id, ord=new.ord) - self.updateCache(fids, type="fact") cardIds = self.db.list( "select id from cards where fid in %s" % ids2str(fids)) @@ -863,11 +848,10 @@ ord = (select ord from templates where id = tid), mod = :now where tid in %s""" % strids, now=time.time()) - # Caches: q/a, facts.cache and fdata.csum + # Q/A generation ########################################################################## - def updateCache(self, ids=None, type="card"): - "Update cache after facts or models changed." + def renderQA(self, ids=None, type="card"): # gather metadata if type == "card": where = "and c.id in " + ids2str(ids) @@ -886,10 +870,10 @@ where tid in %s""" % strids, now=time.time()) for t in m.templates: templs[t.id] = t groups = dict(self.db.all("select id, name from groups")) - return [self._formatQA(mods[row[2]], templs[row[3]], groups[row[4]], row) + return [self._renderQA(mods[row[2]], templs[row[3]], groups[row[4]], row) for row in self._qaData(where)] - def _formatQA(self, model, template, gname, data, filters=True): + def _renderQA(self, model, template, gname, data, filters=True): "Returns hash of id, question, answer." # data is [cid, fid, mid, tid, gid, tags, flds, data] # unpack fields and create dict @@ -913,10 +897,10 @@ where tid in %s""" % strids, now=time.time()) d = dict(id=data[0]) for (type, format) in (("q", template.qfmt), ("a", template.afmt)): # if filters: - # fields = runFilter("formatQA.pre", fields, , self) + # fields = runFilter("renderQA.pre", fields, , self) html = anki.template.render(format, fields) # if filters: - # d[type] = runFilter("formatQA.post", html, fields, meta, self) + # d[type] = runFilter("renderQA.post", html, fields, meta, self) self.media.registerText(html) d[type] = html return d @@ -1009,7 +993,7 @@ insert or ignore into tags (mod, name) values (%d, :t)""" % intTime(), self.db.executemany(""" update facts set tags = :t, mod = :n where id = :id""", [fix(row) for row in res]) # update q/a cache - self.updateCache(fids, type="fact") + self.registerTags(parseTags(tags)) self.finishProgress() def deleteTags(self, ids, tags): @@ -1067,16 +1051,6 @@ update facts set tags = :t, mod = :n where id = :id""", [fix(row) for row in res def disableProgressHandler(self): self.progressHandlerEnabled = False - # Notifications - ########################################################################## - - def notify(self, msg): - "Send a notice to all listeners, or display on stdout." - if hookEmpty("notify"): - pass - else: - runHook("notify", msg) - # File-related ########################################################################## diff --git a/anki/facts.py b/anki/facts.py index a6de3cf79..d906643a7 100644 --- a/anki/facts.py +++ b/anki/facts.py @@ -5,7 +5,7 @@ import time from anki.errors import AnkiError from anki.utils import stripHTMLMedia, fieldChecksum, intTime, \ - addTags, deleteTags, joinFields, splitFields, ids2str + addTags, deleteTags, joinFields, splitFields, ids2str, parseTags class Fact(object): @@ -48,6 +48,7 @@ insert or replace into facts values (?, ?, ?, ?, ?, ?, ?, ?)""", sfld, self.data) self.id = res.lastrowid self.updateFieldChecksums() + self.deck.registerTags(parseTags(self.tags)) def joinedFields(self): return joinFields(self._fields) diff --git a/anki/hooks.py b/anki/hooks.py index fc91c85ad..c43e104bf 100644 --- a/anki/hooks.py +++ b/anki/hooks.py @@ -45,9 +45,6 @@ def removeHook(hook, func): if func in hook: hook.remove(func) -def hookEmpty(hook): - return not _hooks.get(hook) - # Instrumenting ############################################################################## diff --git a/anki/media.py b/anki/media.py index 053deda70..7a6c364a4 100644 --- a/anki/media.py +++ b/anki/media.py @@ -177,7 +177,7 @@ If a file with the same name exists, return a unique name.""" return unicodedata.normalize('NFD', s) return s # generate q/a and look through all references - for p in self.deck.updateCache(type="all"): + for p in self.deck.renderQA(type="all"): for type in ("q", "a"): for f in self.mediaFiles(p[type]): normrefs[norm(f)] = True diff --git a/anki/models.py b/anki/models.py index d92d3c1c6..451cf8321 100644 --- a/anki/models.py +++ b/anki/models.py @@ -49,9 +49,6 @@ insert or replace into models values (?, ?, ?, ?, ?, ?)""", self.id = ret.lastrowid [t._flush() for t in self.templates] - def updateCache(self): - self.deck.updateCache([self.id], "model") - def _getID(self): if not self.id: # flush so we can get our DB id diff --git a/anki/sched.py b/anki/sched.py index 14e9c4175..5bc050dfa 100644 --- a/anki/sched.py +++ b/anki/sched.py @@ -31,11 +31,11 @@ class Scheduler(object): self.resetConf() t = time.time() self.resetLearn() - print "lrn %0.2fms" % ((time.time() - t)*1000); t = time.time() + #print "lrn %0.2fms" % ((time.time() - t)*1000); t = time.time() self.resetReview() - print "rev %0.2fms" % ((time.time() - t)*1000); t = time.time() + #print "rev %0.2fms" % ((time.time() - t)*1000); t = time.time() self.resetNew() - print "new %0.2fms" % ((time.time() - t)*1000); t = time.time() + #print "new %0.2fms" % ((time.time() - t)*1000); t = time.time() def answerCard(self, card, ease): if card.queue == 0: diff --git a/tests/test_media.py b/tests/test_media.py index 485287f00..1687acd5d 100644 --- a/tests/test_media.py +++ b/tests/test_media.py @@ -76,7 +76,7 @@ def test_db(): m = deck.currentModel() m.templates[0].afmt=u'' m.flush() - m.updateCache() + deck.renderQA(type="all") assert deck.db.scalar("select count() from media") == 2 def test_deckIntegration():