def localSummary(self): cardIds = self.cardIds() cStrIds = ids2str(cardIds) cards = self.deck.db.all(""" select id, modified from cards where id in %s""" % cStrIds) notes = self.deck.db.all(""" select notes.id, notes.modified from cards, notes where notes.id = cards.noteId and cards.id in %s""" % cStrIds) models = self.deck.db.all(""" select models.id, models.modified from models, notes where notes.modelId = models.id and notes.id in %s""" % ids2str([f[0] for f in notes])) media = self.deck.db.all(""" select id, modified from media""") return { # cards "cards": cards, "delcards": [], # notes "notes": notes, "delnotes": [], # models "models": models, "delmodels": [], # media "media": media, "delmedia": [], }
def _findField(self, token, isNeg): field = value = '' parts = token.split(':', 1); field = parts[0].lower() value = "%" + parts[1].replace("*", "%") + "%" # find models that have that field mods = {} for m in self.col.models.all(): for f in m['flds']: if f['name'].lower() == field: mods[str(m['id'])] = (m, f['ord']) if not mods: # nothing has that field self.lims['valid'] = False return # gather nids regex = value.replace("_", ".").replace("%", ".*") nids = [] 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())), "%" if self.full else value): flds = splitFields(flds) ord = mods[str(mid)][1] strg = flds[ord] if self.full: strg = stripHTML(strg) if re.search(regex, strg): nids.append(id) extra = "not" if isNeg else "" self.lims['preds'].append(""" n.mid in %s and n.id %s in %s""" % ( ids2str(mods.keys()), extra, ids2str(nids)))
def rewriteIds(self, remote): "Rewrite local IDs so they don't conflict with server version." conf = simplejson.loads(remote['deck'][9]) # cards id = self.deck.db.scalar( "select min(id) from cards where crt > ?", self.deck.lastSync) if id < conf['nextCid']: diff = conf['nextCid'] - id ids = self.deck.db.list( "select id from cards where crt > ?", self.deck.lastSync) sids = ids2str(ids) self.deck.db.execute( "update revlog set cid = cid + ? where cid in "+sids, diff) self.deck.db.execute( "update cards set id = id + ? where id in "+sids, diff) # facts id = self.deck.db.scalar( "select min(id) from facts where crt > ?", self.deck.lastSync) if id < conf['nextFid']: diff = conf['nextFid'] - id ids = self.deck.db.list( "select id from facts where crt > ?", self.deck.lastSync) sids = ids2str(ids) self.deck.db.execute( "update fsums set fid = fid + ? where fid in "+sids, diff) self.deck.db.execute( "update facts set id = id + ? where id in "+sids, diff)
def doExport(self, file): cardIds = self.cardIds() notes = self.deck.db.all(""" select noteId, value, notes.created from notes, fields where notes.id in (select distinct noteId from cards where cards.id in %s) and notes.id = fields.noteId order by noteId, ordinal""" % ids2str(cardIds)) txt = "" if self.includeTags: self.noteTags = dict(self.deck.db.all( "select id, tags from notes where id in %s" % ids2str([note[0] for note in notes]))) groups = itertools.groupby(notes, itemgetter(0)) groups = [[x for x in y[1]] for y in groups] groups = [(group[0][2], "\t".join([self.escapeText(x[1]) for x in group]) + self.tags(group[0][0])) for group in groups] groups.sort(key=itemgetter(0)) out = [ret[1] for ret in groups] self.count = len(out) out = "\n".join(out) file.write(out.encode("utf-8")) self.deck.finishProgress()
def localSummary(self): cardIds = self.cardIds() cStrIds = ids2str(cardIds) cards = self.deck.s.all(""" select id, modified from cards where id in %s""" % cStrIds) facts = self.deck.s.all(""" select facts.id, facts.modified from cards, facts where facts.id = cards.factId and cards.id in %s""" % cStrIds) models = self.deck.s.all(""" select models.id, models.modified from models, facts where facts.modelId = models.id and facts.id in %s""" % ids2str([f[0] for f in facts])) media = self.deck.s.all(""" select id, created from media""") return { # cards "cards": cards, "delcards": [], # facts "facts": facts, "delfacts": [], # models "models": models, "delmodels": [], # media "media": media, "delmedia": [], }
def _addFlagFieldsForTemplate(self, m, nids, tmpl): cids = self.col.db.list( "select id from cards where nid in %s and ord = ?" % ids2str(nids), tmpl['ord']) if len(cids) == len(nids): # not selectively used return # add a flag field name = tmpl['name'] have = [f['name'] for f in m['flds']] while name in have: name += "_" f = self.col.models.newField(name) self.col.models.addField(m, f) # find the notes that have that card haveNids = self.col.db.list( "select nid from cards where id in "+ids2str(cids)) # add "y" to the appended field for those notes self.col.db.execute( "update notes set flds = flds || 'y' where id in "+ids2str( haveNids)) # wrap the template in a conditional tmpl['qfmt'] = "{{#%s}}\n%s\n{{/%s}}" % ( f['name'], tmpl['qfmt'], f['name']) return True
def _findField(self, field, val): field = field.lower() val = val.replace("*", "%") # find models that have that field mods = {} for m in self.col.models.all(): for f in m['flds']: if f['name'].lower() == field: mods[str(m['id'])] = (m, f['ord']) if not mods: # nothing has that field return # gather nids regex = val.replace("_", ".").replace("%", ".*") nids = [] 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())), "%"+val+"%"): flds = splitFields(flds) ord = mods[str(mid)][1] strg = flds[ord] try: if re.search("(?i)^"+regex+"$", strg): nids.append(id) except sre_constants.error: return if not nids: return return "n.id in %s" % ids2str(nids)
def _findField(self, token, isNeg): field = value = '' parts = token.split(':', 1); field = parts[0].lower() value = "%" + parts[1].replace("*", "%") + "%" # find models that have that field mods = {} for m in self.deck.models().values(): for f in m.fields: if f['name'].lower() == field: mods[m.id] = (m, f['ord']) if not mods: # nothing has that field self.lims['valid'] = False return # gather fids regex = value.replace("%", ".*") fids = [] for (id,mid,flds) in self.deck.db.execute(""" select id, mid, flds from facts where mid in %s and flds like ? escape '\\'""" % ( ids2str(mods.keys())), "%" if self.full else value): flds = splitFields(flds) ord = mods[mid][1] str = flds[ord] if self.full: str = stripHTML(str) if re.search(regex, str): fids.append(id) extra = "not" if isNeg else "" self.lims['fact'].append("id %s in %s" % (extra, ids2str(fids)))
def basicCheck(self): "Basic integrity check for syncing. True if ok." # cards without notes if self.db.scalar( """ select 1 from cards where nid not in (select id from notes) limit 1""" ): return # notes without cards or models if self.db.scalar( """ select 1 from notes where id not in (select distinct nid from cards) or mid not in %s limit 1""" % ids2str(self.models.ids()) ): return # invalid ords for m in self.models.all(): # ignore clozes if m["type"] != MODEL_STD: continue if self.db.scalar( """ select 1 from cards where ord not in %s and nid in ( select id from notes where mid = ?) limit 1""" % ids2str([t["ord"] for t in m["tmpls"]]), m["id"], ): return return True
def doExport(self, file): cardIds = self.cardIds() self.deck.startProgress() self.deck.updateProgress(_("Exporting...")) facts = self.deck.s.all(""" select factId, value, facts.created from facts, fields where facts.id in (select distinct factId from cards where cards.id in %s) and facts.id = fields.factId order by factId, ordinal""" % ids2str(cardIds)) txt = "" self.deck.updateProgress() if self.includeTags: self.factTags = dict(self.deck.s.all( "select id, tags from facts where id in %s" % ids2str([fact[0] for fact in facts]))) groups = itertools.groupby(facts, itemgetter(0)) groups = [[x for x in y[1]] for y in groups] groups = [(group[0][2], "\t".join([self.escapeText(x[1]) for x in group]) + self.tags(group[0][0])) for group in groups] self.deck.updateProgress() groups.sort(key=itemgetter(0)) out = [ret[1] for ret in groups] self.count = len(out) out = "\n".join(out) file.write(out.encode("utf-8")) self.deck.finishProgress()
def genKanjiSets(self): self.kanjiSets = [set([]) for g in self.kanjiGrades] mids = self.deck.s.column0( ''' select id from models where tags like "%Japanese%"''' ) fmids = [] for f in KANJI_FIELDS: fmids2 = self.deck.s.column0("select id from fieldModels where name = :f", f=f) fmids.extend(fmids2) all = "".join( self.deck.s.column0( """ select value from cards, fields, facts where cards.reps > 0 and cards.factId = fields.factId and cards.factId = facts.id and facts.modelId in %s and fields.fieldModelId in %s """ % (ids2str(mids), ids2str(fmids)) ) ) for u in all: if isKanji(u): self.kanjiSets[self.kanjiGrade(u)].add(u)
def cids(self, did, children=False, include_from_dynamic=False): deck_ids = [did] + ([deck_id for _, deck_id in self.children(did)] if children else []) request = "select id from cards where did in {}" + ("or odid in {}" if include_from_dynamic else "") parameters = (ids2str(deck_ids),) + ((ids2str(deck_ids),) if include_from_dynamic else tuple()) return self.col.db.list(request.format(*parameters))
def resetCards(self, ids): "Completely reset cards for export." nonNew = self.col.db.list( "select id from cards where id in %s and (queue != 0 or type != 0)" % ids2str(ids)) self.col.db.execute( "update cards set reps=0, lapses=0 where id in " + ids2str(nonNew)) self.forgetCards(nonNew)
def doImport(self): "Import." random = self.deck.newCardOrder == NEW_CARDS_RANDOM num = 4 if random: num += 1 src = DeckStorage.Deck(self.file, backup=False) client = SyncClient(self.deck) server = SyncServer(src) client.setServer(server) # if there is a conflict, sync local -> src client.localTime = self.deck.modified client.remoteTime = 0 src.s.execute("update facts set modified = 1") src.s.execute("update models set modified = 1") src.s.execute("update cards set modified = 1") src.s.execute("update media set created = 1") self.deck.db.flush() # set up a custom change list and sync lsum = client.summary(0) self._clearDeleted(lsum) rsum = server.summary(0) self._clearDeleted(rsum) payload = client.genPayload((lsum, rsum)) # no need to add anything to src payload['added-models'] = [] payload['added-cards'] = [] payload['added-facts'] = {'facts': [], 'fields': []} assert payload['deleted-facts'] == [] assert payload['deleted-cards'] == [] assert payload['deleted-models'] == [] res = server.applyPayload(payload) client.applyPayloadReply(res) copyLocalMedia(server.deck, client.deck) # add tags fids = [f[0] for f in res['added-facts']['facts']] self.deck.addTags(fids, self.tagsToAdd) # mark import material as newly added self.deck.db.execute( "update cards set modified = :t where id in %s" % ids2str([x[0] for x in res['added-cards']]), t=time.time()) self.deck.db.execute( "update facts set modified = :t where id in %s" % ids2str([x[0] for x in res['added-facts']['facts']]), t=time.time()) self.deck.db.execute( "update models set modified = :t where id in %s" % ids2str([x['id'] for x in res['added-models']]), t=time.time()) # update total and refresh self.total = len(res['added-facts']['facts']) src.s.rollback() src.engine.dispose() # randomize? if random: self.deck.randomizeNewCards([x[0] for x in res['added-cards']]) self.deck.flushMod()
def getMedia(self, ids, updateCreated=False): size = self.deck.s.scalar( "select sum(size) from media where id in %s" % ids2str(ids)) if ids: self.mediaSyncPending = True if updateCreated: created = time.time() else: created = "created" return [tuple(row) for row in self.deck.s.all(""" select id, filename, size, %s, originalPath, description from media where id in %s""" % (created, ids2str(ids)))]
def renderQA(self, ids=None, type="card"): # gather metadata if type == "card": where = "and c.id in " + ids2str(ids) elif type == "note": where = "and f.id in " + ids2str(ids) elif type == "model": where = "and m.id in " + ids2str(ids) elif type == "all": where = "" else: raise Exception() return [self._renderQA(row) for row in self._qaData(where)]
def fixIntegrity(self): "Fix possible problems and rebuild caches." problems = [] self.save() oldSize = os.stat(self.path)[stat.ST_SIZE] if self.db.scalar("pragma integrity_check") != "ok": return (_("Collection is corrupt. Please see the manual."), False) # note types with a missing model ids = self.db.list(""" select id from notes where mid not in """ + ids2str(self.models.ids())) if ids: print self.db.list("select distinct mid from notes where id in " + ids2str(ids)) problems.append( ngettext("Deleted %d note with missing note type.", "Deleted %d notes with missing note type.", len(ids)) % len(ids)) self.remNotes(ids) # delete any notes with missing cards ids = self.db.list(""" select id from notes where id not in (select distinct nid from cards)""") if ids: cnt = len(ids) problems.append( ngettext("Deleted %d note with no cards.", "Deleted %d notes with no cards.", cnt) % cnt) self._remNotes(ids) # tags self.tags.registerNotes() # field cache for m in self.models.all(): self.updateFieldCache(self.models.nids(m)) # new card position self.conf['nextPos'] = self.db.scalar( "select max(due)+1 from cards where type = 0") or 0 # reviews should have a reasonable due # ids = self.db.list( "select id from cards where queue = 2 and due > 10000") if ids: problems.append("Reviews had incorrect due date.") self.db.execute( "update cards set due = 0, mod = ?, usn = ? where id in %s" % ids2str(ids), intTime(), self.usn()) # and finally, optimize self.optimize() newSize = os.stat(self.path)[stat.ST_SIZE] txt = _("Database rebuilt and optimized.") ok = not problems problems.append(txt) self.save() return ("\n".join(problems), ok)
def delCards(self, ids): "Bulk delete cards by ID." if not ids: return sids = ids2str(ids) fids = self.db.list("select fid from cards where id in "+sids) # remove cards self._logDels(ids, DEL_CARD) self.db.execute("delete from cards where id in "+sids) self.db.execute("delete from revlog where cid in "+sids) # then facts fids = self.db.list(""" select id from facts where id in %s and id not in (select fid from cards)""" % ids2str(fids)) self._delFacts(fids)
def remCards(self, ids): "Bulk delete cards by ID." if not ids: return sids = ids2str(ids) nids = self.db.list("select nid from cards where id in "+sids) # remove cards self._logRem(ids, REM_CARD) self.db.execute("delete from cards where id in "+sids) self.db.execute("delete from revlog where cid in "+sids) # then notes nids = self.db.list(""" select id from notes where id in %s and id not in (select nid from cards)""" % ids2str(nids)) self._remNotes(nids)
def _revlogLimit(self): lim = self.deck.qconf['groups'] if self.selective and lim: return ("cid in (select id from cards where gid in %s)" % ids2str(lim)) else: return ""
def showMatching(self, force=True): if not self.sortKey: self.cards = [] return # sorting if not self.searchStr: ads = "" self.lastSearch = "" else: if (self.searchStr.strip() == self.lastSearch.strip() and not force): # just whitespace return QApplication.instance().processEvents() self.lastSearch = self.searchStr ids = self.deck.findCards(self.searchStr) ads = "cards.id in %s" % ids2str(ids) sort = "" if isinstance(self.sortKey, types.StringType): # card property if self.sortKey == "fact": sort = "order by facts.created, cards.created" else: sort = "order by cards." + self.sortKey if self.sortKey in ("question", "answer"): sort += " collate nocase" if self.sortKey == "fact": query = """ select cards.id from cards, facts where cards.factId = facts.id """ if ads: query += "and " + ads + " " else: query = "select id from cards " if ads: query += "where %s " % ads query += sort else: # field value ret = self.deck.s.all( "select id, numeric from fieldModels where name = :name", name=self.sortKey[1]) fields = ",".join([str(x[0]) for x in ret]) # if multiple models have the same field, use the first numeric bool numeric = ret[0][1] if numeric: order = "cast(fields.value as real)" else: order = "fields.value collate nocase" if ads: ads = " and " + ads query = ("select cards.id " "from fields, cards where fields.fieldModelId in (%s) " "and fields.factId = cards.factId" + ads + " order by cards.ordinal, %s") % (fields, order) # run the query self.cards = self.deck.s.all(query) if self.deck.getInt('reverseOrder'): self.cards.reverse() self.reset()
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 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 sortCards(self, cids, start=1, step=1, shuffle=False, shift=False): scids = ids2str(cids) now = intTime() nids = self.col.db.list( ("select distinct nid from cards where type = 0 and id in %s " "order by nid") % scids) if not nids: # no new cards return # determine nid ordering due = {} if shuffle: random.shuffle(nids) for c, nid in enumerate(nids): due[nid] = start+c*step high = start+c*step # shift? if shift: 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.col.db.execute(""" update cards set mod=?, usn=?, due=due+? where id not in %s and due >= ? and queue = 0""" % scids, now, self.col.usn(), shiftby, low) # reorder cards d = [] 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.col.usn(), cid=id)) self.col.db.executemany( "update cards set due=:due,mod=:now,usn=:usn where id = :cid""", d)
def sortFieldOrderCids(did): dids = [did] for name, id in mw.col.decks.children(did): dids.append(id) return mw.col.db.list(""" select c.id from cards c, notes n where did in %s and c.nid = n.id order by n.sfld""" % ids2str(dids))
def bulkAdd(self, ids, tags, add=True): "Add tags in bulk. TAGS is space-separated." newTags = self.split(tags) if not newTags: return # cache tag names self.register(newTags) # find notes missing the tags if add: l = "tags not " fn = self.addToStr else: l = "tags " fn = self.remFromStr lim = " or ".join( [l+"like :_%d" % c for c, t in enumerate(newTags)]) res = self.col.db.all( "select id, tags from notes where id in %s and (%s)" % ( ids2str(ids), lim), **dict([("_%d" % x, '%% %s %%' % y) for x, y in enumerate(newTags)])) # update tags nids = [] def fix(row): nids.append(row[0]) return {'id': row[0], 't': fn(tags, row[1]), 'n':intTime(), '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])
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 suspendCards(self, ids): "Suspend cards." self.remFromDyn(ids) self.removeFailed(ids) self.col.db.execute( "update cards set queue=-1,mod=?,usn=? where id in "+ ids2str(ids), intTime(), self.col.usn())
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 _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 remNotes(self, ids: Iterable[int]) -> None: """Deletes notes with the given IDs.""" self.remCards( self.db.list("select id from cards where nid in " + ids2str(ids)))
def genCards(self, nids: List[int]) -> List[int]: "Generate cards for non-empty templates, return ids to remove." # build map of (nid,ord) so we don't create dupes snids = ids2str(nids) have: Dict[int, Dict[int, int]] = {} dids: Dict[int, Optional[int]] = {} dues: Dict[int, int] = {} for id, nid, ord, did, due, odue, odid, type in self.db.execute( "select id, nid, ord, did, due, odue, odid, type from cards where nid in " + snids): # existing cards if nid not in have: have[nid] = {} have[nid][ord] = id # if in a filtered deck, add new cards to original deck if odid != 0: did = odid # and their dids if nid in dids: if dids[nid] and dids[nid] != did: # cards are in two or more different decks; revert to # model default dids[nid] = None else: # first card or multiple cards in same deck dids[nid] = did # save due if odid != 0: due = odue if nid not in dues and type == 0: # Add due to new card only if it's the due of a new sibling dues[nid] = due # build cards for each note data = [] ts = maxID(self.db) now = intTime() rem = [] usn = self.usn() for nid, mid, flds in self.db.execute( "select id, mid, flds from notes where id in " + snids): model = self.models.get(mid) assert model avail = self.models.availOrds(model, flds) did = dids.get(nid) or model["did"] due = dues.get(nid) # add any missing cards for t in self._tmplsFromOrds(model, avail): doHave = nid in have and t["ord"] in have[nid] if not doHave: # check deck is not a cram deck did = t["did"] or did if self.decks.isDyn(did): did = 1 # if the deck doesn't exist, use default instead did = self.decks.get(did)["id"] # use sibling due# if there is one, else use a new id if due is None: due = self.nextID("pos") data.append((ts, nid, did, t["ord"], now, usn, due)) ts += 1 # note any cards that need removing if nid in have: for ord, id in list(have[nid].items()): if ord not in avail: rem.append(id) # bulk update self.db.executemany( """ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""", data, ) return rem
def fixIntegrity(self) -> Tuple[str, bool]: """Fix possible problems and rebuild caches. Returns tuple of (error: str, ok: bool). 'ok' will be true if no problems were found. """ problems = [] # problems that don't require a full sync syncable_problems = [] self.save() oldSize = os.stat(self.path)[stat.ST_SIZE] if self.db.scalar("pragma integrity_check") != "ok": return (_("Collection is corrupt. Please see the manual."), False) # note types with a missing model ids = self.db.list(""" select id from notes where mid not in """ + ids2str(self.models.ids())) if ids: problems.append( ngettext( "Deleted %d note with missing note type.", "Deleted %d notes with missing note type.", len(ids), ) % len(ids)) self.remNotes(ids) # for each model for m in self.models.all(): for t in m["tmpls"]: if t["did"] == "None": t["did"] = None problems.append(_("Fixed AnkiDroid deck override bug.")) self.models.save(m, updateReqs=False) if m["type"] == MODEL_STD: # model with missing req specification if "req" not in m: self.models._updateRequired(m) problems.append(_("Fixed note type: %s") % m["name"]) # cards with invalid ordinal ids = self.db.list( """ select id from cards where ord not in %s and nid in ( select id from notes where mid = ?)""" % ids2str([t["ord"] for t in m["tmpls"]]), m["id"], ) if ids: problems.append( ngettext( "Deleted %d card with missing template.", "Deleted %d cards with missing template.", len(ids), ) % len(ids)) self.remCards(ids) # notes with invalid field count ids = [] for id, flds in self.db.execute( "select id, flds from notes where mid = ?", m["id"]): if (flds.count("\x1f") + 1) != len(m["flds"]): ids.append(id) if ids: problems.append( ngettext( "Deleted %d note with wrong field count.", "Deleted %d notes with wrong field count.", len(ids), ) % len(ids)) self.remNotes(ids) # delete any notes with missing cards ids = self.db.list(""" select id from notes where id not in (select distinct nid from cards)""") if ids: cnt = len(ids) problems.append( ngettext( "Deleted %d note with no cards.", "Deleted %d notes with no cards.", cnt, ) % cnt) self._remNotes(ids) # cards with missing notes ids = self.db.list(""" select id from cards where nid not in (select id from notes)""") if ids: cnt = len(ids) problems.append( ngettext( "Deleted %d card with missing note.", "Deleted %d cards with missing note.", cnt, ) % cnt) self.remCards(ids) # cards with odue set when it shouldn't be ids = self.db.list(""" select id from cards where odue > 0 and (type=1 or queue=2) and not odid""") if ids: cnt = len(ids) problems.append( ngettext( "Fixed %d card with invalid properties.", "Fixed %d cards with invalid properties.", cnt, ) % cnt) self.db.execute("update cards set odue=0 where id in " + ids2str(ids)) # cards with odid set when not in a dyn deck dids = [id for id in self.decks.allIds() if not self.decks.isDyn(id)] ids = self.db.list(""" select id from cards where odid > 0 and did in %s""" % ids2str(dids)) if ids: cnt = len(ids) problems.append( ngettext( "Fixed %d card with invalid properties.", "Fixed %d cards with invalid properties.", cnt, ) % cnt) self.db.execute("update cards set odid=0, odue=0 where id in " + ids2str(ids)) # notes with non-normalized tags cnt = self._normalize_tags() if cnt > 0: syncable_problems.append( self.tr(TR.DATABASE_CHECK_FIXED_NON_NORMALIZED_TAGS, count=cnt)) # tags self.tags.registerNotes() # field cache for m in self.models.all(): self.updateFieldCache(self.models.nids(m)) # new cards can't have a due position > 32 bits, so wrap items over # 2 million back to 1 million self.db.execute( """ update cards set due=1000000+due%1000000,mod=?,usn=? where due>=1000000 and type=0""", intTime(), self.usn(), ) rowcount = self.db.scalar("select changes()") if rowcount: problems.append( "Found %d new cards with a due number >= 1,000,000 - consider repositioning them in the Browse screen." % rowcount) # new card position self.conf["nextPos"] = ( self.db.scalar("select max(due)+1 from cards where type = 0") or 0) # reviews should have a reasonable due # ids = self.db.list( "select id from cards where queue = 2 and due > 100000") if ids: problems.append("Reviews had incorrect due date.") self.db.execute( "update cards set due = ?, ivl = 1, mod = ?, usn = ? where id in %s" % ids2str(ids), self.sched.today, intTime(), self.usn(), ) # v2 sched had a bug that could create decimal intervals self.db.execute( "update cards set ivl=round(ivl),due=round(due) where ivl!=round(ivl) or due!=round(due)" ) rowcount = self.db.scalar("select changes()") if rowcount: problems.append("Fixed %d cards with v2 scheduler bug." % rowcount) self.db.execute( "update revlog set ivl=round(ivl),lastIvl=round(lastIvl) where ivl!=round(ivl) or lastIvl!=round(lastIvl)" ) rowcount = self.db.scalar("select changes()") if rowcount: problems.append( "Fixed %d review history entries with v2 scheduler bug." % rowcount) # models if self.models.ensureNotEmpty(): problems.append("Added missing note type.") # and finally, optimize self.optimize() newSize = os.stat(self.path)[stat.ST_SIZE] txt = _("Database rebuilt and optimized.") ok = not problems problems.append(txt) # if any problems were found, force a full sync if not ok: self.modSchema(check=False) self.save() problems.extend(syncable_problems) return ("\n".join(problems), ok)
def _recoverOrphans(self) -> None: dids = list(self.decks.keys()) mod = self.col.db.mod self.col.db.execute("update cards set did = 1 where did not in " + ids2str(dids)) self.col.db.mod = mod
def updateCards(self, cards): if not cards: return # FIXME: older clients won't send this, so this is temp compat code def getType(row): if len(row) > 36: return row[36] if row[15]: return 1 elif row[14]: return 0 return 2 dlist = [{ 'id': c[0], 'factId': c[1], 'cardModelId': c[2], 'created': c[3], 'modified': c[4], 'tags': c[5], 'ordinal': c[6], 'priority': c[7], 'interval': c[8], 'lastInterval': c[9], 'due': c[10], 'lastDue': c[11], 'factor': c[12], 'firstAnswered': c[13], 'reps': c[14], 'successive': c[15], 'averageTime': c[16], 'reviewTime': c[17], 'youngEase0': c[18], 'youngEase1': c[19], 'youngEase2': c[20], 'youngEase3': c[21], 'youngEase4': c[22], 'matureEase0': c[23], 'matureEase1': c[24], 'matureEase2': c[25], 'matureEase3': c[26], 'matureEase4': c[27], 'yesCount': c[28], 'noCount': c[29], 'question': c[30], 'answer': c[31], 'lastFactor': c[32], 'spaceUntil': c[33], 'type': c[34], 'combinedDue': c[35], 'rd': getType(c) } for c in cards] self.deck.s.execute( """ insert or replace into cards (id, factId, cardModelId, created, modified, tags, ordinal, priority, interval, lastInterval, due, lastDue, factor, firstAnswered, reps, successive, averageTime, reviewTime, youngEase0, youngEase1, youngEase2, youngEase3, youngEase4, matureEase0, matureEase1, matureEase2, matureEase3, matureEase4, yesCount, noCount, question, answer, lastFactor, spaceUntil, type, combinedDue, relativeDelay, isDue) values (:id, :factId, :cardModelId, :created, :modified, :tags, :ordinal, :priority, :interval, :lastInterval, :due, :lastDue, :factor, :firstAnswered, :reps, :successive, :averageTime, :reviewTime, :youngEase0, :youngEase1, :youngEase2, :youngEase3, :youngEase4, :matureEase0, :matureEase1, :matureEase2, :matureEase3, :matureEase4, :yesCount, :noCount, :question, :answer, :lastFactor, :spaceUntil, :type, :combinedDue, :rd, 0)""", dlist) self.deck.s.statement("delete from cardsDeleted where cardId in %s" % ids2str([c[0] for c in cards]))
def getOneWayCards(self, ids): "The minimum information necessary to generate one way cards." return self.deck.s.all( "select id, factId, cardModelId, ordinal, created from cards " "where id in %s" % ids2str(ids))
def exportInto(self, path): # sched info+v2 scheduler not compatible w/ older clients self._v2sched = self.col.schedVer() != 1 and self.includeSched # create a new collection at the target try: os.unlink(path) except (IOError, OSError): pass self.dst = Collection(path) self.src = self.col # find cards if not self.did: cids = self.src.db.list("select id from cards") else: cids = self.src.decks.cids(self.did, children=True) # copy cards, noting used nids nids = {} data = [] for row in self.src.db.execute( "select * from cards where id in "+ids2str(cids)): nids[row[1]] = True data.append(row) # clear flags row = list(row) row[-2] = 0 self.dst.db.executemany( "insert into cards values (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)", data) # notes strnids = ids2str(list(nids.keys())) notedata = [] for row in self.src.db.all( "select * from notes where id in "+strnids): # remove system tags if not exporting scheduling info if not self.includeSched: row = list(row) row[5] = self.removeSystemTags(row[5]) notedata.append(row) self.dst.db.executemany( "insert into notes values (?,?,?,?,?,?,?,?,?,?,?)", notedata) # models used by the notes mids = self.dst.db.list("select distinct mid from notes where id in "+ strnids) # card history and revlog if self.includeSched: data = self.src.db.all( "select * from revlog where cid in "+ids2str(cids)) self.dst.db.executemany( "insert into revlog values (?,?,?,?,?,?,?,?,?)", data) else: # need to reset card state self.dst.sched.resetCards(cids) # models - start with zero self.dst.models.models = {} for m in self.src.models.all(): if int(m['id']) in mids: self.dst.models.update(m) # decks if not self.did: dids = [] else: dids = [self.did] + [ x[1] for x in self.src.decks.children(self.did)] dconfs = {} for d in self.src.decks.all(): if str(d['id']) == "1": continue if dids and d['id'] not in dids: continue if not d['dyn'] and d['conf'] != 1: if self.includeSched: dconfs[d['conf']] = True if not self.includeSched: # scheduling not included, so reset deck settings to default d = dict(d) d['conf'] = 1 self.dst.decks.update(d) # copy used deck confs for dc in self.src.decks.allConf(): if dc['id'] in dconfs: self.dst.decks.updateConf(dc) # find used media media = {} self.mediaDir = self.src.media.dir() if self.includeMedia: for row in notedata: flds = row[6] mid = row[2] for file in self.src.media.filesInStr(mid, flds): # skip files in subdirs if file != os.path.basename(file): continue media[file] = True if self.mediaDir: for fname in os.listdir(self.mediaDir): path = os.path.join(self.mediaDir, fname) if os.path.isdir(path): continue if fname.startswith("_"): # Scan all models in mids for reference to fname for m in self.src.models.all(): if int(m['id']) in mids: if self._modelHasMedia(m, fname): media[fname] = True break self.mediaFiles = list(media.keys()) self.dst.crt = self.src.crt # todo: tags? self.count = self.dst.cardCount() self.dst.setMod() self.postExport() self.dst.close()
def remove_notes_by_card(self, card_ids: List[int]) -> None: if hooks.notes_will_be_deleted.count(): nids = self.db.list("select nid from cards where id in " + ids2str(card_ids)) hooks.notes_will_be_deleted(self, nids) self.backend.remove_notes(note_ids=[], card_ids=card_ids)
def get_sql_anki_cids_from_evernote_guids(guids): return "c.nid IN " + ids2str(get_anki_note_ids_from_evernote_guids(guids))
def _deckLimit(self) -> str: return ids2str( self.col.decks.deck_and_child_ids(self.col.decks.get_current_id()))
def remFromDyn(self, cids): self.emptyDyn(None, "id in %s and odid" % ids2str(cids))
def unsuspendCards(self, ids): "Unsuspend cards." self.col.db.execute( "update cards set queue=type,mod=?,usn=? " "where queue = -1 and id in "+ ids2str(ids), intTime(), self.col.usn())
def _resetRevCountV2(sched): lim = currentRevLimit(sched) sched.revCount = sched.col.db.scalar(""" select count() from (select id from cards where did in %s and queue = 2 and due <= ? limit %d)""" % ( ids2str(sched.col.decks.active()), lim), sched.today)
def fixIntegrity(self): "Fix possible problems and rebuild caches." problems = [] curs = self.db.cursor() self.save() oldSize = os.stat(self.path)[stat.ST_SIZE] # whether sqlite find a problem in its database if self.db.scalar("pragma integrity_check") != "ok": return (_("Collection is corrupt. Please see the manual."), False) # note types with a missing model ids = self.db.list(""" select id from notes where mid not in """ + ids2str(self.models.ids())) if ids: problems.append( ngettext("Deleted %d note with missing note type.", "Deleted %d notes with missing note type.", len(ids)) % len(ids)) self.remNotes(ids) # for each model for m in self.models.all(): for t in m['tmpls']: if t['did'] == "None": t['did'] = None problems.append(_("Fixed AnkiDroid deck override bug.")) self.models.save(m) if m['type'] == MODEL_STD: # model with missing req specification if 'req' not in m: self.models._updateRequired(m) problems.append(_("Fixed note type: %s") % m['name']) # cards with invalid ordinal ids = self.db.list( """ select id from cards where ord not in %s and nid in ( select id from notes where mid = ?)""" % ids2str([t['ord'] for t in m['tmpls']]), m['id']) if ids: problems.append( ngettext("Deleted %d card with missing template.", "Deleted %d cards with missing template.", len(ids)) % len(ids)) self.remCards(ids) # notes with invalid field count ids = [] for id, flds in self.db.execute( "select id, flds from notes where mid = ?", m['id']): if (flds.count("\x1f") + 1) != len(m['flds']): ids.append(id) if ids: problems.append( ngettext("Deleted %d note with wrong field count.", "Deleted %d notes with wrong field count.", len(ids)) % len(ids)) self.remNotes(ids) # delete any notes with missing cards ids = self.db.list(""" select id from notes where id not in (select distinct nid from cards)""") if ids: cnt = len(ids) problems.append( ngettext("Deleted %d note with no cards.", "Deleted %d notes with no cards.", cnt) % cnt) self._remNotes(ids) # cards with missing notes ids = self.db.list(""" select id from cards where nid not in (select id from notes)""") if ids: cnt = len(ids) problems.append( ngettext("Deleted %d card with missing note.", "Deleted %d cards with missing note.", cnt) % cnt) self.remCards(ids) # cards with odue set when it shouldn't be ids = self.db.list(f""" select id from cards where odue > 0 and (type={CARD_LRN} or queue={CARD_DUE}) and not odid""" ) if ids: cnt = len(ids) problems.append( ngettext("Fixed %d card with invalid properties.", "Fixed %d cards with invalid properties.", cnt) % cnt) self.db.execute("update cards set odue=0 where id in " + ids2str(ids)) # cards with odid set when not in a dyn deck dids = [id for id in self.decks.allIds() if not self.decks.isDyn(id)] ids = self.db.list(""" select id from cards where odid > 0 and did in %s""" % ids2str(dids)) if ids: cnt = len(ids) problems.append( ngettext("Fixed %d card with invalid properties.", "Fixed %d cards with invalid properties.", cnt) % cnt) self.db.execute("update cards set odid=0, odue=0 where id in " + ids2str(ids)) # tags self.tags.registerNotes() # field cache for m in self.models.all(): self.updateFieldCache(self.models.nids(m)) # new cards can't have a due position > 32 bits, so wrap items over # 2 million back to 1 million curs.execute( """ update cards set due=1000000+due%1000000,mod=?,usn=? where due>=1000000 and type=0""", [intTime(), self.usn()]) if curs.rowcount: problems.append( "Found %d new cards with a due number >= 1,000,000 - consider repositioning them in the Browse screen." % curs.rowcount) # new card position self.conf['nextPos'] = self.db.scalar( f"select max(due)+1 from cards where type = {CARD_NEW}") or 0 # reviews should have a reasonable due # ids = self.db.list( "select id from cards where queue = 2 and due > 100000") if ids: problems.append("Reviews had incorrect due date.") self.db.execute( "update cards set due = ?, ivl = 1, mod = ?, usn = ? where id in %s" % ids2str(ids), self.sched.today, intTime(), self.usn()) # v2 sched had a bug that could create decimal intervals curs.execute( "update cards set ivl=round(ivl),due=round(due) where ivl!=round(ivl) or due!=round(due)" ) if curs.rowcount: problems.append("Fixed %d cards with v2 scheduler bug." % curs.rowcount) curs.execute( "update revlog set ivl=round(ivl),lastIvl=round(lastIvl) where ivl!=round(ivl) or lastIvl!=round(lastIvl)" ) if curs.rowcount: problems.append( "Fixed %d review history entries with v2 scheduler bug." % curs.rowcount) # models if self.models.ensureNotEmpty(): problems.append("Added missing note type.") # and finally, optimize self.optimize() newSize = os.stat(self.path)[stat.ST_SIZE] txt = _("Database rebuilt and optimized.") ok = not problems problems.append(txt) # if any problems were found, force a full sync if not ok: self.modSchema(check=False) self.save() return ("\n".join(problems), ok)
def genCards(self, nids): """Ids of cards which needs to be removed. Generate missing cards of a note with id in nids. """ # build map of (nid,ord) so we don't create dupes snids = ids2str(nids) have = { } #Associated to each nid a dictionnary from card's order to card id. dids = { } #Associate to each nid the only deck id containing its cards. Or None if there are multiple decks dues = {} #Associate to each nid the due value of the last card seen. for id, nid, ord, did, due, odue, odid in self.db.execute( "select id, nid, ord, did, due, odue, odid from cards where nid in " + snids): # existing cards if nid not in have: have[nid] = {} have[nid][ord] = id # if in a filtered deck, add new cards to original deck if odid != 0: did = odid # and their dids if nid in dids: if dids[nid] and dids[nid] != did: # cards are in two or more different decks; revert to # model default dids[nid] = None else: # first card or multiple cards in same deck dids[nid] = did # save due if odid != 0: due = odue if nid not in dues: dues[nid] = due # build cards for each note data = [ ] #Tuples for cards to create. Each tuple is newCid, nid, did, ord, now, usn, due ts = maxID(self.db) now = intTime() rem = [] #cards to remove usn = self.usn() for nid, mid, flds in self.db.execute( "select id, mid, flds from notes where id in " + snids): model = self.models.get(mid) avail = self.models.availOrds(model, flds) did = dids.get(nid) or model['did'] due = dues.get(nid) # add any missing cards for t in self._tmplsFromOrds(model, avail): doHave = nid in have and t['ord'] in have[nid] if not doHave: # check deck is not a cram deck did = t['did'] or did if self.decks.isDyn(did): did = 1 # if the deck doesn't exist, use default instead did = self.decks.get(did)['id'] # use sibling due# if there is one, else use a new id if due is None: due = self.nextID("pos") data.append((ts, nid, did, t['ord'], now, usn, due)) ts += 1 # note any cards that need removing if nid in have: for ord, id in list(have[nid].items()): if ord not in avail: rem.append(id) # bulk update self.db.executemany( """ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""", data) return rem
def setUserFlag(self, flag, cids): assert 0 <= flag <= 7 self.db.execute( "update cards set flags = (flags & ~?) | ?, usn=?, mod=? where id in %s" % ids2str(cids), 0b111, flag, self._usn, intTime())
def setUserFlag(self, flag, cids): assert 0 <= flag <= 7 self.db.execute("update cards set flags = (flags & ~?) | ? where id in %s" % ids2str(cids), 0b111, flag)
def getMedia(self, ids): return [ tuple(row) for row in self.deck.s.all(""" select id, filename, size, created, originalPath, description from media where id in %s""" % ids2str(ids)) ]
def remNotes(self, ids): """Removes all cards associated to the notes whose id is in ids""" self.remCards( self.db.list("select id from cards where nid in " + ids2str(ids)))
def _revlogLimit(self): if self.wholeCollection: return "" return ("cid in (select id from cards where did in %s)" % ids2str(self.col.decks.active()))
def totalRevForCurrentDeck(self): return self.col.db.scalar( """ select count() from cards where id in ( select id from cards where did in %s and queue = 2 and due <= ? limit ?)""" % ids2str(self.col.decks.active()), self.today, self.reportLimit)
def getCards( deck, fids ): cis = deck.s.column0( 'select id from cards where factId in %s' % ids2str(fids) ) cs = [ deck.s.query(Card).get( id ) for id in cis ] return cs
def _limit(self) -> Any: if self.wholeCollection: return ids2str([d["id"] for d in self.col.decks.all()]) return self.col.sched._deck_limit()
def _limit(self): if self.wholeCollection: return ids2str([d['id'] for d in self.col.decks.all()]) return self.col.sched._deckLimit()
def fixIntegrity(self): "Fix possible problems and rebuild caches." problems = [] self.save() oldSize = os.stat(self.path)[stat.ST_SIZE] if self.db.scalar("pragma integrity_check") != "ok": return (_("Collection is corrupt. Please see the manual."), False) # note types with a missing model ids = self.db.list(""" select id from notes where mid not in """ + ids2str(self.models.ids())) if ids: problems.append( ngettext("Deleted %d note with missing note type.", "Deleted %d notes with missing note type.", len(ids)) % len(ids)) self.remNotes(ids) # for each model for m in self.models.all(): for t in m['tmpls']: if t['did'] == "None": t['did'] = None problems.append(_("Fixed AnkiDroid deck override bug.")) self.models.save(m) if m['type'] == MODEL_STD: # model with missing req specification if 'req' not in m: self.models._updateRequired(m) problems.append(_("Fixed note type: %s") % m['name']) # cards with invalid ordinal ids = self.db.list(""" select id from cards where ord not in %s and nid in ( select id from notes where mid = ?)""" % ids2str([t['ord'] for t in m['tmpls']]), m['id']) if ids: problems.append( ngettext("Deleted %d card with missing template.", "Deleted %d cards with missing template.", len(ids)) % len(ids)) self.remCards(ids) # notes with invalid field count ids = [] for id, flds in self.db.execute( "select id, flds from notes where mid = ?", m['id']): if (flds.count("\x1f") + 1) != len(m['flds']): ids.append(id) if ids: problems.append( ngettext("Deleted %d note with wrong field count.", "Deleted %d notes with wrong field count.", len(ids)) % len(ids)) self.remNotes(ids) # delete any notes with missing cards ids = self.db.list(""" select id from notes where id not in (select distinct nid from cards)""") if ids: cnt = len(ids) problems.append( ngettext("Deleted %d note with no cards.", "Deleted %d notes with no cards.", cnt) % cnt) self._remNotes(ids) # cards with missing notes ids = self.db.list(""" select id from cards where nid not in (select id from notes)""") if ids: cnt = len(ids) problems.append( ngettext("Deleted %d card with missing note.", "Deleted %d cards with missing note.", cnt) % cnt) self.remCards(ids) # cards with odue set when it shouldn't be ids = self.db.list(""" select id from cards where odue > 0 and (type=1 or queue=2) and not odid""") if ids: cnt = len(ids) problems.append( ngettext("Fixed %d card with invalid properties.", "Fixed %d cards with invalid properties.", cnt) % cnt) self.db.execute("update cards set odue=0 where id in "+ ids2str(ids)) # cards with odid set when not in a dyn deck dids = [id for id in self.decks.allIds() if not self.decks.isDyn(id)] ids = self.db.list(""" select id from cards where odid > 0 and did in %s""" % ids2str(dids)) if ids: cnt = len(ids) problems.append( ngettext("Fixed %d card with invalid properties.", "Fixed %d cards with invalid properties.", cnt) % cnt) self.db.execute("update cards set odid=0, odue=0 where id in "+ ids2str(ids)) # tags self.tags.registerNotes() # field cache for m in self.models.all(): self.updateFieldCache(self.models.nids(m)) # new cards can't have a due position > 32 bits self.db.execute(""" update cards set due = 1000000, mod = ?, usn = ? where due > 1000000 and queue = 0""", intTime(), self.usn()) # new card position self.conf['nextPos'] = self.db.scalar( "select max(due)+1 from cards where type = 0") or 0 # reviews should have a reasonable due # ids = self.db.list( "select id from cards where queue = 2 and due > 100000") if ids: problems.append("Reviews had incorrect due date.") self.db.execute( "update cards set due = ?, ivl = 1, mod = ?, usn = ? where id in %s" % ids2str(ids), self.sched.today, intTime(), self.usn()) # and finally, optimize self.optimize() newSize = os.stat(self.path)[stat.ST_SIZE] txt = _("Database rebuilt and optimized.") ok = not problems problems.append(txt) # if any problems were found, force a full sync if not ok: self.modSchema(check=False) self.save() return ("\n".join(problems), ok)
def haveBuried(self): sdids = ids2str(self.col.decks.active()) cnt = self.col.db.scalar( "select 1 from cards where queue = -2 and did in %s limit 1" % sdids) return not not cnt
def genCards(self, nids): "Generate cards for non-empty templates, return ids to remove." # build map of (nid,ord) so we don't create dupes snids = ids2str(nids) have = {} dids = {} for id, nid, ord, did, odid in self.db.execute( "select id, nid, ord, did, odid from cards where nid in "+snids): # existing cards if nid not in have: have[nid] = {} have[nid][ord] = id # if in a filtered deck, add new cards to original deck if odid != 0: did = odid # and their dids if nid in dids: if dids[nid] and dids[nid] != did: # cards are in two or more different decks; revert to # model default dids[nid] = None else: # first card or multiple cards in same deck dids[nid] = did # build cards for each note data = [] ts = maxID(self.db) now = intTime() rem = [] usn = self.usn() for nid, mid, flds in self.db.execute( "select id, mid, flds from notes where id in "+snids): model = self.models.get(mid) avail = self.models.availOrds(model, flds) did = dids.get(nid) or model['did'] # add any missing cards for t in self._tmplsFromOrds(model, avail): doHave = nid in have and t['ord'] in have[nid] if not doHave: # check deck is not a cram deck did = t['did'] or did if self.decks.isDyn(did): did = 1 # if the deck doesn't exist, use default instead did = self.decks.get(did)['id'] # we'd like to use the same due# as sibling cards, but we # can't retrieve that quickly, so we give it a new id # instead data.append((ts, nid, did, t['ord'], now, usn, self.nextID("pos"))) ts += 1 # note any cards that need removing if nid in have: for ord, id in list(have[nid].items()): if ord not in avail: rem.append(id) # bulk update self.db.executemany(""" insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""", data) return rem
def remNotes(self, ids): self.remCards(self.db.list("select id from cards where nid in "+ ids2str(ids)))
def _deckLimit(self): return ids2str(self.col.decks.active())
def setDeck(self, cids, did): self.col.db.execute( "update cards set did=?,usn=?,mod=? where id in "+ ids2str(cids), did, self.col.usn(), intTime())