mirror of
https://github.com/ankitects/anki.git
synced 2025-09-18 22:12:21 -04:00
collection
This commit is contained in:
parent
912e1bad03
commit
f69ef52845
1 changed files with 35 additions and 32 deletions
|
@ -31,6 +31,9 @@ import anki.template
|
||||||
import anki.find
|
import anki.find
|
||||||
from typing import Any, List, Optional, Tuple, Union, Dict
|
from typing import Any, List, Optional, Tuple, Union, Dict
|
||||||
|
|
||||||
|
from anki.cards import Card
|
||||||
|
from anki.db import DB
|
||||||
|
from anki.notes import Note
|
||||||
defaultConf = {
|
defaultConf = {
|
||||||
# review options
|
# review options
|
||||||
'activeDecks': [1],
|
'activeDecks': [1],
|
||||||
|
@ -59,7 +62,7 @@ def timezoneOffset() -> int:
|
||||||
# this is initialized by storage.Collection
|
# this is initialized by storage.Collection
|
||||||
class _Collection:
|
class _Collection:
|
||||||
|
|
||||||
def __init__(self, db, server=False, log=False) -> None:
|
def __init__(self, db: DB, server: bool = False, log: bool = False) -> None:
|
||||||
self._debugLog = log
|
self._debugLog = log
|
||||||
self.db = db
|
self.db = db
|
||||||
self.path = db._path
|
self.path = db._path
|
||||||
|
@ -111,7 +114,7 @@ class _Collection:
|
||||||
|
|
||||||
self.sched = Scheduler(self)
|
self.sched = Scheduler(self)
|
||||||
|
|
||||||
def changeSchedulerVer(self, ver) -> None:
|
def changeSchedulerVer(self, ver: int) -> None:
|
||||||
if ver == self.schedVer():
|
if ver == self.schedVer():
|
||||||
return
|
return
|
||||||
if ver not in self.supportedSchedulerVersions:
|
if ver not in self.supportedSchedulerVersions:
|
||||||
|
@ -162,7 +165,7 @@ DB operations and the deck/tag/model managers do this automatically, so this
|
||||||
is only necessary if you modify properties of this object or the conf dict."""
|
is only necessary if you modify properties of this object or the conf dict."""
|
||||||
self.db.mod = True
|
self.db.mod = True
|
||||||
|
|
||||||
def flush(self, mod=None) -> None:
|
def flush(self, mod: None = None) -> None:
|
||||||
"Flush state to DB, updating mod time."
|
"Flush state to DB, updating mod time."
|
||||||
self.mod = intTime(1000) if mod is None else mod
|
self.mod = intTime(1000) if mod is None else mod
|
||||||
self.db.execute(
|
self.db.execute(
|
||||||
|
@ -171,7 +174,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
self.crt, self.mod, self.scm, self.dty,
|
self.crt, self.mod, self.scm, self.dty,
|
||||||
self._usn, self.ls, json.dumps(self.conf))
|
self._usn, self.ls, json.dumps(self.conf))
|
||||||
|
|
||||||
def save(self, name=None, mod=None) -> None:
|
def save(self, name: Optional[str] = None, mod: None = None) -> None:
|
||||||
"Flush, commit DB, and take out another write lock."
|
"Flush, commit DB, and take out another write lock."
|
||||||
# let the managers conditionally flush
|
# let the managers conditionally flush
|
||||||
self.models.flush()
|
self.models.flush()
|
||||||
|
@ -198,7 +201,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
self.db.execute("update col set mod=mod")
|
self.db.execute("update col set mod=mod")
|
||||||
self.db.mod = mod
|
self.db.mod = mod
|
||||||
|
|
||||||
def close(self, save=True) -> None:
|
def close(self, save: bool = True) -> None:
|
||||||
"Disconnect from DB."
|
"Disconnect from DB."
|
||||||
if self.db:
|
if self.db:
|
||||||
if save:
|
if save:
|
||||||
|
@ -227,7 +230,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
self.load()
|
self.load()
|
||||||
self.lock()
|
self.lock()
|
||||||
|
|
||||||
def modSchema(self, check) -> None:
|
def modSchema(self, check: bool) -> None:
|
||||||
"Mark schema modified. Call this first so user can abort if necessary."
|
"Mark schema modified. Call this first so user can abort if necessary."
|
||||||
if not self.schemaChanged():
|
if not self.schemaChanged():
|
||||||
if check and not runFilter("modSchema", True):
|
if check and not runFilter("modSchema", True):
|
||||||
|
@ -264,16 +267,16 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
# Object creation helpers
|
# Object creation helpers
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def getCard(self, id) -> anki.cards.Card:
|
def getCard(self, id: int) -> anki.cards.Card:
|
||||||
return anki.cards.Card(self, id)
|
return anki.cards.Card(self, id)
|
||||||
|
|
||||||
def getNote(self, id) -> anki.notes.Note:
|
def getNote(self, id: int) -> anki.notes.Note:
|
||||||
return anki.notes.Note(self, id=id)
|
return anki.notes.Note(self, id=id)
|
||||||
|
|
||||||
# Utils
|
# Utils
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def nextID(self, type, inc=True) -> Any:
|
def nextID(self, type: str, inc: bool = True) -> Any:
|
||||||
type = "next"+type.capitalize()
|
type = "next"+type.capitalize()
|
||||||
id = self.conf.get(type, 1)
|
id = self.conf.get(type, 1)
|
||||||
if inc:
|
if inc:
|
||||||
|
@ -287,7 +290,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
# Deletion logging
|
# Deletion logging
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _logRem(self, ids, type) -> None:
|
def _logRem(self, ids: List[int], type: int) -> None:
|
||||||
self.db.executemany("insert into graves values (%d, ?, %d)" % (
|
self.db.executemany("insert into graves values (%d, ?, %d)" % (
|
||||||
self.usn(), type), ([x] for x in ids))
|
self.usn(), type), ([x] for x in ids))
|
||||||
|
|
||||||
|
@ -297,11 +300,11 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
def noteCount(self) -> Any:
|
def noteCount(self) -> Any:
|
||||||
return self.db.scalar("select count() from notes")
|
return self.db.scalar("select count() from notes")
|
||||||
|
|
||||||
def newNote(self, forDeck=True) -> anki.notes.Note:
|
def newNote(self, forDeck: bool = True) -> anki.notes.Note:
|
||||||
"Return a new note with the current model."
|
"Return a new note with the current model."
|
||||||
return anki.notes.Note(self, self.models.current(forDeck))
|
return anki.notes.Note(self, self.models.current(forDeck))
|
||||||
|
|
||||||
def addNote(self, note) -> int:
|
def addNote(self, note: Note) -> int:
|
||||||
"Add a note to the collection. Return number of new cards."
|
"Add a note to the collection. Return number of new cards."
|
||||||
# check we have card models available, then save
|
# check we have card models available, then save
|
||||||
cms = self.findTemplates(note)
|
cms = self.findTemplates(note)
|
||||||
|
@ -321,7 +324,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
self.remCards(self.db.list("select id from cards where nid in "+
|
self.remCards(self.db.list("select id from cards where nid in "+
|
||||||
ids2str(ids)))
|
ids2str(ids)))
|
||||||
|
|
||||||
def _remNotes(self, ids) -> None:
|
def _remNotes(self, ids: List[int]) -> None:
|
||||||
"Bulk delete notes by ID. Don't call this directly."
|
"Bulk delete notes by ID. Don't call this directly."
|
||||||
if not ids:
|
if not ids:
|
||||||
return
|
return
|
||||||
|
@ -335,13 +338,13 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
# Card creation
|
# Card creation
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def findTemplates(self, note) -> List:
|
def findTemplates(self, note: Note) -> List:
|
||||||
"Return (active), non-empty templates."
|
"Return (active), non-empty templates."
|
||||||
model = note.model()
|
model = note.model()
|
||||||
avail = self.models.availOrds(model, joinFields(note.fields))
|
avail = self.models.availOrds(model, joinFields(note.fields))
|
||||||
return self._tmplsFromOrds(model, avail)
|
return self._tmplsFromOrds(model, avail)
|
||||||
|
|
||||||
def _tmplsFromOrds(self, model, avail) -> List:
|
def _tmplsFromOrds(self, model: Dict[str, Any], avail: List[int]) -> List:
|
||||||
ok = []
|
ok = []
|
||||||
if model['type'] == MODEL_STD:
|
if model['type'] == MODEL_STD:
|
||||||
for t in model['tmpls']:
|
for t in model['tmpls']:
|
||||||
|
@ -355,7 +358,7 @@ crt=?, mod=?, scm=?, dty=?, usn=?, ls=?, conf=?""",
|
||||||
ok.append(t)
|
ok.append(t)
|
||||||
return ok
|
return ok
|
||||||
|
|
||||||
def genCards(self, nids) -> List:
|
def genCards(self, nids: List[int]) -> List:
|
||||||
"Generate cards for non-empty templates, return ids to remove."
|
"Generate cards for non-empty templates, return ids to remove."
|
||||||
# build map of (nid,ord) so we don't create dupes
|
# build map of (nid,ord) so we don't create dupes
|
||||||
snids = ids2str(nids)
|
snids = ids2str(nids)
|
||||||
|
@ -429,7 +432,7 @@ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
|
||||||
# type 0 - when previewing in add dialog, only non-empty
|
# type 0 - when previewing in add dialog, only non-empty
|
||||||
# type 1 - when previewing edit, only existing
|
# type 1 - when previewing edit, only existing
|
||||||
# type 2 - when previewing in models dialog, all templates
|
# type 2 - when previewing in models dialog, all templates
|
||||||
def previewCards(self, note, type=0, did=None) -> List:
|
def previewCards(self, note: Note, type: int = 0, did: None = None) -> List:
|
||||||
if type == 0:
|
if type == 0:
|
||||||
cms = self.findTemplates(note)
|
cms = self.findTemplates(note)
|
||||||
elif type == 1:
|
elif type == 1:
|
||||||
|
@ -443,7 +446,7 @@ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
|
||||||
cards.append(self._newCard(note, template, 1, flush=False, did=did))
|
cards.append(self._newCard(note, template, 1, flush=False, did=did))
|
||||||
return cards
|
return cards
|
||||||
|
|
||||||
def _newCard(self, note, template, due, flush=True, did=None) -> anki.cards.Card:
|
def _newCard(self, note: Note, template: Dict[str, Any], due: int, flush: bool = True, did: None = None) -> anki.cards.Card:
|
||||||
"Create a new card."
|
"Create a new card."
|
||||||
card = anki.cards.Card(self)
|
card = anki.cards.Card(self)
|
||||||
card.nid = note.id
|
card.nid = note.id
|
||||||
|
@ -470,7 +473,7 @@ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
|
||||||
card.flush()
|
card.flush()
|
||||||
return card
|
return card
|
||||||
|
|
||||||
def _dueForDid(self, did, due: int) -> int:
|
def _dueForDid(self, did: int, due: int) -> int:
|
||||||
conf = self.decks.confForDid(did)
|
conf = self.decks.confForDid(did)
|
||||||
# in order due?
|
# in order due?
|
||||||
if conf['new']['order'] == NEW_CARDS_DUE:
|
if conf['new']['order'] == NEW_CARDS_DUE:
|
||||||
|
@ -491,7 +494,7 @@ insert into cards values (?,?,?,?,?,?,0,0,?,0,0,0,0,0,0,0,0,"")""",
|
||||||
def cardCount(self) -> Any:
|
def cardCount(self) -> Any:
|
||||||
return self.db.scalar("select count() from cards")
|
return self.db.scalar("select count() from cards")
|
||||||
|
|
||||||
def remCards(self, ids, notes=True) -> None:
|
def remCards(self, ids: List[int], notes: bool = True) -> None:
|
||||||
"Bulk delete cards by ID."
|
"Bulk delete cards by ID."
|
||||||
if not ids:
|
if not ids:
|
||||||
return
|
return
|
||||||
|
@ -526,11 +529,11 @@ where c.nid = n.id and c.id in %s group by nid""" % ids2str(cids)):
|
||||||
# Field checksums and sorting fields
|
# Field checksums and sorting fields
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def _fieldData(self, snids) -> Any:
|
def _fieldData(self, snids: str) -> Any:
|
||||||
return self.db.execute(
|
return self.db.execute(
|
||||||
"select id, mid, flds from notes where id in "+snids)
|
"select id, mid, flds from notes where id in "+snids)
|
||||||
|
|
||||||
def updateFieldCache(self, nids) -> None:
|
def updateFieldCache(self, nids: List[int]) -> None:
|
||||||
"Update field checksums and sort cache, after find&replace, etc."
|
"Update field checksums and sort cache, after find&replace, etc."
|
||||||
snids = ids2str(nids)
|
snids = ids2str(nids)
|
||||||
r = []
|
r = []
|
||||||
|
@ -564,7 +567,7 @@ where c.nid = n.id and c.id in %s group by nid""" % ids2str(cids)):
|
||||||
return [self._renderQA(row)
|
return [self._renderQA(row)
|
||||||
for row in self._qaData(where)]
|
for row in self._qaData(where)]
|
||||||
|
|
||||||
def _renderQA(self, data, qfmt=None, afmt=None) -> Dict:
|
def _renderQA(self, data: Tuple[int,int,int,int,int,str,str,int], qfmt: None = None, afmt: None = None) -> Dict:
|
||||||
"Returns hash of id, question, answer."
|
"Returns hash of id, question, answer."
|
||||||
# data is [cid, nid, mid, did, ord, tags, flds, cardFlags]
|
# data is [cid, nid, mid, did, ord, tags, flds, cardFlags]
|
||||||
# unpack fields and create dict
|
# unpack fields and create dict
|
||||||
|
@ -619,7 +622,7 @@ from cards c, notes f
|
||||||
where c.nid == f.id
|
where c.nid == f.id
|
||||||
%s""" % where)
|
%s""" % where)
|
||||||
|
|
||||||
def _flagNameFromCardFlags(self, flags) -> str:
|
def _flagNameFromCardFlags(self, flags: int) -> str:
|
||||||
flag = flags & 0b111
|
flag = flags & 0b111
|
||||||
if not flag:
|
if not flag:
|
||||||
return ""
|
return ""
|
||||||
|
@ -628,22 +631,22 @@ where c.nid == f.id
|
||||||
# Finding cards
|
# Finding cards
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def findCards(self, query, order=False) -> Any:
|
def findCards(self, query: str, order: Union[bool, str] = False) -> Any:
|
||||||
return anki.find.Finder(self).findCards(query, order)
|
return anki.find.Finder(self).findCards(query, order)
|
||||||
|
|
||||||
def findNotes(self, query) -> Any:
|
def findNotes(self, query: str) -> Any:
|
||||||
return anki.find.Finder(self).findNotes(query)
|
return anki.find.Finder(self).findNotes(query)
|
||||||
|
|
||||||
def findReplace(self, nids, src, dst, regex=None, field=None, fold=True) -> int:
|
def findReplace(self, nids: List[int], src: str, dst: str, regex: Optional[bool] = None, field: Optional[str] = None, fold: bool = True) -> int:
|
||||||
return anki.find.findReplace(self, nids, src, dst, regex, field, fold)
|
return anki.find.findReplace(self, nids, src, dst, regex, field, fold)
|
||||||
|
|
||||||
def findDupes(self, fieldName, search="") -> List[Tuple[Any, list]]:
|
def findDupes(self, fieldName: str, search: str = "") -> List[Tuple[Any, list]]:
|
||||||
return anki.find.findDupes(self, fieldName, search)
|
return anki.find.findDupes(self, fieldName, search)
|
||||||
|
|
||||||
# Stats
|
# Stats
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def cardStats(self, card) -> str:
|
def cardStats(self, card: Card) -> str:
|
||||||
from anki.stats import CardStats
|
from anki.stats import CardStats
|
||||||
return CardStats(self, card).report()
|
return CardStats(self, card).report()
|
||||||
|
|
||||||
|
@ -687,7 +690,7 @@ where c.nid == f.id
|
||||||
else:
|
else:
|
||||||
self._undoOp()
|
self._undoOp()
|
||||||
|
|
||||||
def markReview(self, card) -> None:
|
def markReview(self, card: Card) -> None:
|
||||||
old = []
|
old = []
|
||||||
if self._undo:
|
if self._undo:
|
||||||
if self._undo[0] == 1:
|
if self._undo[0] == 1:
|
||||||
|
@ -724,7 +727,7 @@ where c.nid == f.id
|
||||||
self.sched.reps -= 1
|
self.sched.reps -= 1
|
||||||
return c.id
|
return c.id
|
||||||
|
|
||||||
def _markOp(self, name) -> None:
|
def _markOp(self, name: Optional[str]) -> None:
|
||||||
"Call via .save()"
|
"Call via .save()"
|
||||||
if name:
|
if name:
|
||||||
self._undo = [2, name]
|
self._undo = [2, name]
|
||||||
|
@ -947,7 +950,7 @@ and type=0""", [intTime(), self.usn()])
|
||||||
# Card Flags
|
# Card Flags
|
||||||
##########################################################################
|
##########################################################################
|
||||||
|
|
||||||
def setUserFlag(self, flag, cids) -> None:
|
def setUserFlag(self, flag: int, cids: List[int]) -> None:
|
||||||
assert 0 <= flag <= 7
|
assert 0 <= flag <= 7
|
||||||
self.db.execute("update cards set flags = (flags & ~?) | ?, usn=?, mod=? where id in %s" %
|
self.db.execute("update cards set flags = (flags & ~?) | ?, usn=?, mod=? where id in %s" %
|
||||||
ids2str(cids), 0b111, flag, self.usn(), intTime())
|
ids2str(cids), 0b111, flag, self.usn(), intTime())
|
||||||
|
|
Loading…
Reference in a new issue