def _checkDeckTree(self): decks = self.col.decks.all() decks.sort(key=operator.itemgetter('name')) names = set() for deck in decks: # two decks with the same name? if deck['name'] in names: self.col.log("fix duplicate deck name", deck['name']) deck['name'] += "%d" % intTime(1000) self.save(deck) # ensure no sections are blank if not all(deck['name'].split("::")): self.col.log("fix deck with missing sections", deck['name']) deck['name'] = "recovered%d" % intTime(1000) self.save(deck) # immediate parent must exist if "::" in deck['name']: immediateParent = "::".join(deck['name'].split("::")[:-1]) if immediateParent not in names: self.col.log("fix deck with missing parent", deck['name']) self._ensureParents(deck['name']) names.add(immediateParent) names.add(deck['name'])
def updateData(self, n, id, sflds): self._ids.append(id) if not self.processFields(n, sflds): return if self._tagsMapped: self.col.tags.register(n.tags) tags = self.col.tags.join(n.tags) return [ intTime(), self.col.usn(), n.fieldsStr, tags, id, n.fieldsStr, tags ] else: return [intTime(), self.col.usn(), n.fieldsStr, id, n.fieldsStr]
def unsuspendCards(self, ids): "Unsuspend cards." self.col.log(ids) self.col.db.execute( "update cards set queue=type,mod=?,usn=? " "where queue = -1 and id in " + ids2str(ids), intTime(), self.col.usn())
def flush(self, mod=None): "Flush state to DB, updating mod time." self.mod = intTime(1000) if mod is None else mod self.db.execute( """update col set crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""", self.crt, self.mod, self.scm, self.dty, self._usn, self.ls, json.dumps(self.conf))
def remTemplate(self, m, template): "False if removing template would leave orphan notes." assert len(m['tmpls']) > 1 # find cards using this template ord = m['tmpls'].index(template) 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.col.db.scalar(""" select nid, count() from cards where nid in (select nid from cards where id in %s) group by nid having count() < 2 limit 1""" % ids2str(cids)): return False # ok to proceed; remove cards self.col.modSchema(check=True) self.col.remCards(cids) # shift ordinals self.col.db.execute(""" update cards set ord = ord - 1, usn = ?, mod = ? where nid in (select id from notes where mid = ?) and ord > ?""", self.col.usn(), intTime(), m['id'], ord) m['tmpls'].remove(template) self._updateTemplOrds(m) self.save(m) return True
def id(self, name, create=True, type=None): "Add a deck with NAME. Reuse deck if already exists. Return id as int." if type is None: type = defaultDeck name = name.replace('"', '') name = unicodedata.normalize("NFC", name) for id, g in list(self.decks.items()): if unicodedata.normalize("NFC", g['name'].lower()) == name.lower(): return int(id) if not create: return None g = copy.deepcopy(type) if "::" in name: # not top level; ensure all parents exist name = self._ensureParents(name) g['name'] = name while 1: id = intTime(1000) if str(id) not in self.decks: break g['id'] = id self.decks[str(id)] = g self.save(g) self.maybeAddToActive() runHook("newDeck") return int(id)
def _undoReview(self): data = self._undo[2] wasLeech = self._undo[3] c = data.pop() if not data: self.clearUndo() # remove leech tag if it didn't have it before if not wasLeech and c.note().hasTag("leech"): c.note().delTag("leech") c.note().flush() # write old data c.flush() # and delete revlog entry last = self.db.scalar( "select id from revlog where cid = ? " "order by id desc limit 1", c.id) self.db.execute("delete from revlog where id = ?", last) # restore any siblings self.db.execute( "update cards set queue=type,mod=?,usn=? where queue=-2 and nid=?", intTime(), self.usn(), c.nid) # and finally, update daily counts n = 1 if c.queue == 3 else c.queue type = ("new", "lrn", "rev")[n] self.sched._updateStats(c, type, -1) self.sched.reps -= 1 return c.id
def modSchema(self, check): "Mark schema modified. Call this first so user can abort if necessary." if not self.schemaChanged(): if check and not runFilter("modSchema", True): raise AnkiError("abortSchemaMod") self.scm = intTime(1000) self.setMod()
def _changeCards(self, nids, oldModel, newModel, map): d = [] deleted = [] for (cid, ord) in self.col.db.execute( "select id, ord from cards where nid in "+ids2str(nids)): # if the src model is a cloze, we ignore the map, as the gui # doesn't currently support mapping them if oldModel['type'] == MODEL_CLOZE: new = ord if newModel['type'] != MODEL_CLOZE: # if we're mapping to a regular note, we need to check if # the destination ord is valid if len(newModel['tmpls']) <= ord: new = None else: # mapping from a regular note, so the map should be valid new = map[ord] if new is not None: d.append(dict( cid=cid,new=new,u=self.col.usn(),m=intTime())) else: deleted.append(cid) self.col.db.executemany( "update cards set ord=:new,usn=:u,mod=:m where id=:cid", d) self.col.remCards(deleted)
def _burySiblings(self, card): toBury = [] nconf = self._newConf(card) buryNew = nconf.get("bury", True) rconf = self._revConf(card) buryRev = rconf.get("bury", True) # loop through and remove from queues for cid, queue in self.col.db.execute( """ select id, queue from cards where nid=? and id!=? and (queue=0 or (queue=2 and due<=?))""", card.nid, card.id, self.today): if queue == 2: if buryRev: toBury.append(cid) # if bury disabled, we still discard to give same-day spacing try: self._revQueue.remove(cid) except ValueError: pass else: # if bury disabled, we still discard to give same-day spacing if buryNew: toBury.append(cid) try: self._newQueue.remove(cid) except ValueError: pass # then bury if toBury: self.col.db.execute( "update cards set queue=-2,mod=?,usn=? where id in " + ids2str(toBury), intTime(), self.col.usn()) self.col.log(toBury)
def fix(row): nids.append(row[0]) return { 'id': row[0], 't': fn(tags, row[1]), 'n': intTime(), 'u': self.col.usn() }
def buryCards(self, cids): self.col.log(cids) self.remFromDyn(cids) self.removeLrn(cids) self.col.db.execute( """ update cards set queue=-2,mod=?,usn=? where id in """ + ids2str(cids), intTime(), self.col.usn())
def unburyCardsForDeck(self): sids = ids2str(self.col.decks.active()) self.col.log( self.col.db.list( "select id from cards where queue = -2 and did in %s" % sids)) self.col.db.execute( "update cards set mod=?,usn=?,queue=type where queue = -2 and did in %s" % sids, intTime(), self.col.usn())
def suspendCards(self, ids): "Suspend cards." self.col.log(ids) self.remFromDyn(ids) self.removeLrn(ids) self.col.db.execute( "update cards set queue=-1,mod=?,usn=? where id in " + ids2str(ids), intTime(), self.col.usn())
def meta(self): return dict( mod=self.col.mod, scm=self.col.scm, usn=self.col._usn, ts=intTime(), musn=0, msg="", cont=True )
def save(self, m=None, templates=False): "Mark M modified if provided, and schedule registry flush." if m and m['id']: m['mod'] = intTime() m['usn'] = self.col.usn() self._updateRequired(m) if templates: self._syncTemplates(m) self.changed = True runHook("newModel")
def _transformFields(self, m, fn): # model hasn't been added yet? if not m['id']: return r = [] 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.col.usn(), id)) self.col.db.executemany( "update notes set flds=?,mod=?,usn=? where id = ?", r)
def new(self, name): "Create a new model, save it in the registry, and return it." # caller should call save() after modifying m = defaultModel.copy() m['name'] = name m['mod'] = intTime() m['flds'] = [] m['tmpls'] = [] m['tags'] = [] m['id'] = None return m
def _getColVars(db): import anki_lib.collection import anki_lib.decks g = copy.deepcopy(anki_lib.decks.defaultDeck) g['id'] = 1 g['name'] = _("Default") g['conf'] = 1 g['mod'] = intTime() gc = copy.deepcopy(anki_lib.decks.defaultConf) gc['id'] = 1 return g, gc, anki_lib.collection.defaultConf.copy()
def _lrnForDeck(self, did): cnt = self.col.db.scalar( """ select sum(left/1000) from (select left from cards where did = ? and queue = 1 and due < ? limit ?)""", did, intTime() + self.col.conf['collapseTime'], self.reportLimit) or 0 return cnt + self.col.db.scalar( """ select count() from (select 1 from cards where did = ? and queue = 3 and due <= ? limit ?)""", did, self.today, self.reportLimit)
def _leftToday(self, delays, left, now=None): "The number of steps that can be completed by the day cutoff." if not now: now = intTime() delays = delays[-left:] ok = 0 for i in range(len(delays)): now += delays[i] * 60 if now > self.dayCutoff: break ok = i return ok + 1
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 col.models.all(): for f in m['flds']: if f['name'].lower() == field.lower(): mmap[str(m['id'])] = f['ord'] if not mmap: return 0 # find and gather replacements if not regex: src = re.escape(src) if fold: src = "(?i)" + src regex = re.compile(src) def repl(str): return re.sub(regex, dst, str) d = [] snids = ids2str(nids) nids = [] for nid, mid, flds in col.db.execute( "select id, mid, flds from notes where id in " + snids): origFlds = flds # does it match? sflds = splitFields(flds) if field: try: ord = mmap[str(mid)] sflds[ord] = repl(sflds[ord]) except KeyError: # note doesn't have that field continue else: for c in range(len(sflds)): sflds[c] = repl(sflds[c]) flds = joinFields(sflds) if flds != origFlds: nids.append(nid) d.append(dict(nid=nid, flds=flds, u=col.usn(), m=intTime())) if not d: return 0 # replace col.db.executemany( "update notes set flds=:flds,mod=:m,usn=:u where id=:nid", d) col.updateFieldCache(nids) col.genCards(nids) return len(d)
def confId(self, name, cloneFrom=None): "Create a new configuration and return id." if cloneFrom is None: cloneFrom = defaultConf c = copy.deepcopy(cloneFrom) while 1: id = intTime(1000) if str(id) not in self.dconf: break c['id'] = id c['name'] = name self.dconf[str(id)] = c self.save(c) return id
def flushSched(self): self.mod = intTime() self.usn = self.col.usn() # bug checks if self.queue == 2 and self.odue and not self.col.decks.isDyn( self.did): runHook("odueInvalid") assert self.due < 4294967296 self.col.db.execute( """update cards set mod=?, usn=?, type=?, queue=?, due=?, ivl=?, factor=?, reps=?, lapses=?, left=?, odue=?, odid=?, did=? where id = ?""", self.mod, self.usn, self.type, self.queue, self.due, self.ivl, self.factor, self.reps, self.lapses, self.left, self.odue, self.odid, self.did, self.id) self.col.log(self)
def flush(self): self.mod = intTime() self.usn = self.col.usn() # bug check if self.queue == 2 and self.odue and not self.col.decks.isDyn( self.did): runHook("odueInvalid") assert self.due < 4294967296 self.col.db.execute( """ insert or replace into cards values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""", self.id, self.nid, self.did, self.ord, self.mod, self.usn, self.type, self.queue, self.due, self.ivl, self.factor, self.reps, self.lapses, self.left, self.odue, self.odid, self.flags, self.data) self.col.log(self)
def log(self, *args, **kwargs): if not self._debugLog: return def customRepr(x): if isinstance(x, str): return x return pprint.pformat(x) path, num, fn, y = traceback.extract_stack(limit=2 + kwargs.get("stack", 0))[0] buf = "[%s] %s:%s(): %s" % (intTime(), os.path.basename(path), fn, ", ".join([customRepr(x) for x in args])) self._logHnd.write(buf + "\n") if devMode: print(buf)
def newData(self, n): id = self._nextID self._nextID += 1 self._ids.append(id) if not self.processFields(n): return # note id for card updates later for ord, c in list(n.cards.items()): self._cards.append((id, ord, c)) self.col.tags.register(n.tags) return [ id, guid64(), self.model['id'], intTime(), self.col.usn(), self.col.tags.join(n.tags), n.fieldsStr, "", "", 0, "" ]
def _changeNotes(self, nids, newModel, map): d = [] nfields = len(newModel['flds']) for (nid, flds) in self.col.db.execute( "select id, flds from notes where id in "+ids2str(nids)): newflds = {} flds = splitFields(flds) for old, new in list(map.items()): newflds[new] = flds[old] flds = [] for c in range(nfields): flds.append(newflds.get(c, "")) flds = joinFields(flds) d.append(dict(nid=nid, flds=flds, mid=newModel['id'], 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.col.updateFieldCache(nids)
def moveTemplate(self, m, template, idx): oldidx = m['tmpls'].index(template) if oldidx == idx: return oldidxs = dict((id(t), t['ord']) for t in m['tmpls']) m['tmpls'].remove(template) m['tmpls'].insert(idx, template) self._updateTemplOrds(m) # generate change map map = [] for t in m['tmpls']: map.append("when ord = %d then %d" % (oldidxs[id(t)], t['ord'])) # apply self.save(m) 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.col.usn(), intTime(), m['id'])
def removeLrn(self, ids=None): "Remove cards from the learning queues." if ids: extra = " and id in " + ids2str(ids) else: # benchmarks indicate it's about 10x faster to search all decks # with the index than scan the table extra = " and did in " + ids2str(self.col.decks.allIds()) # review cards in relearning self.col.db.execute(""" update cards set due = odue, queue = 2, mod = %d, usn = %d, odue = 0 where queue in (1,3) and type = 2 %s """ % (intTime(), self.col.usn(), extra)) # new cards in learning self.forgetCards( self.col.db.list("select id from cards where queue in (1,3) %s" % extra))