Beispiel #1
0
    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'])
Beispiel #2
0
 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]
Beispiel #3
0
 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())
Beispiel #4
0
    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))
Beispiel #5
0
    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
Beispiel #6
0
 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)
Beispiel #7
0
 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
Beispiel #8
0
 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()
Beispiel #9
0
 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)
Beispiel #10
0
    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)
Beispiel #11
0
 def fix(row):
     nids.append(row[0])
     return {
         'id': row[0],
         't': fn(tags, row[1]),
         'n': intTime(),
         'u': self.col.usn()
     }
Beispiel #12
0
    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())
Beispiel #13
0
 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())
Beispiel #14
0
 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())
Beispiel #15
0
 def meta(self):
     return dict(
         mod=self.col.mod,
         scm=self.col.scm,
         usn=self.col._usn,
         ts=intTime(),
         musn=0,
         msg="",
         cont=True
     )
Beispiel #16
0
 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")
Beispiel #17
0
 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)
Beispiel #18
0
 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
Beispiel #19
0
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()
Beispiel #20
0
    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)
Beispiel #21
0
 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
Beispiel #22
0
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)
Beispiel #23
0
 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
Beispiel #24
0
    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)
Beispiel #25
0
    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)
Beispiel #26
0
    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)
Beispiel #27
0
 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, ""
     ]
Beispiel #28
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)
Beispiel #29
0
    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'])
Beispiel #30
0
    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))