remove typings from some other files

not used frequently enough to deal with the mypy errors they're causing
at the moment
This commit is contained in:
Damien Elmes 2019-12-20 16:33:49 +10:00
parent b6b8df2dcf
commit d8d7e78b6b
4 changed files with 167 additions and 173 deletions

View file

@ -101,7 +101,7 @@ acq_reps+ret_reps, lapses, card_type_id from cards"""):
def fields(self): def fields(self):
return self._fields return self._fields
def _mungeField(self, fld) -> str: def _mungeField(self, fld):
# \n -> br # \n -> br
fld = re.sub("\r?\n", "<br>", fld) fld = re.sub("\r?\n", "<br>", fld)
# latex differences # latex differences
@ -110,7 +110,7 @@ acq_reps+ret_reps, lapses, card_type_id from cards"""):
fld = re.sub("<audio src=\"(.+?)\">(</audio>)?", "[sound:\\1]", fld) fld = re.sub("<audio src=\"(.+?)\">(</audio>)?", "[sound:\\1]", fld)
return fld return fld
def _addFronts(self, notes, model=None, fields=("f", "b")) -> None: def _addFronts(self, notes, model=None, fields=("f", "b")):
data = [] data = []
for orig in notes: for orig in notes:
# create a foreign note object # create a foreign note object
@ -135,7 +135,7 @@ acq_reps+ret_reps, lapses, card_type_id from cards"""):
# import # import
self.importNotes(data) self.importNotes(data)
def _addFrontBacks(self, notes) -> None: def _addFrontBacks(self, notes):
m = addBasicModel(self.col) m = addBasicModel(self.col)
m['name'] = "Mnemosyne-FrontBack" m['name'] = "Mnemosyne-FrontBack"
mm = self.col.models mm = self.col.models
@ -145,7 +145,7 @@ acq_reps+ret_reps, lapses, card_type_id from cards"""):
mm.addTemplate(m, t) mm.addTemplate(m, t)
self._addFronts(notes, m) self._addFronts(notes, m)
def _addVocabulary(self, notes) -> None: def _addVocabulary(self, notes):
mm = self.col.models mm = self.col.models
m = mm.new("Mnemosyne-Vocabulary") m = mm.new("Mnemosyne-Vocabulary")
for f in "Expression", "Pronunciation", "Meaning", "Notes": for f in "Expression", "Pronunciation", "Meaning", "Notes":
@ -164,7 +164,7 @@ acq_reps+ret_reps, lapses, card_type_id from cards"""):
mm.add(m) mm.add(m)
self._addFronts(notes, m, fields=("f", "p_1", "m_1", "n")) self._addFronts(notes, m, fields=("f", "p_1", "m_1", "n"))
def _addCloze(self, notes) -> None: def _addCloze(self, notes):
data = [] data = []
notes = list(notes.values()) notes = list(notes.values())
for orig in notes: for orig in notes:

View file

@ -13,10 +13,7 @@ from anki.lang import ngettext
from xml.dom import minidom from xml.dom import minidom
from string import capwords from string import capwords
import re, unicodedata, time import re, unicodedata, time
from typing import Any, List, Optional
from anki.collection import _Collection
from xml.dom.minidom import Element, Text
class SmartDict(dict): class SmartDict(dict):
""" """
See http://www.peterbe.com/plog/SmartDict See http://www.peterbe.com/plog/SmartDict
@ -28,7 +25,7 @@ class SmartDict(dict):
x.get('first_name'). x.get('first_name').
""" """
def __init__(self, *a, **kw) -> None: def __init__(self, *a, **kw):
if a: if a:
if isinstance(type(a[0]), dict): if isinstance(type(a[0]), dict):
kw.update(a[0]) kw.update(a[0])
@ -43,7 +40,7 @@ class SmartDict(dict):
class SuperMemoElement(SmartDict): class SuperMemoElement(SmartDict):
"SmartDict wrapper to store SM Element data" "SmartDict wrapper to store SM Element data"
def __init__(self, *a, **kw) -> None: def __init__(self, *a, **kw):
SmartDict.__init__(self, *a, **kw) SmartDict.__init__(self, *a, **kw)
#default content #default content
self.__dict__['lTitle'] = None self.__dict__['lTitle'] = None
@ -82,7 +79,7 @@ class SupermemoXmlImporter(NoteImporter):
Code should be upgrade to support importing of SM2006 exports. Code should be upgrade to support importing of SM2006 exports.
""" """
def __init__(self, col: _Collection, file: str) -> None: def __init__(self, col, file):
"""Initialize internal varables. """Initialize internal varables.
Pameters to be exposed to GUI are stored in self.META""" Pameters to be exposed to GUI are stored in self.META"""
NoteImporter.__init__(self, col, file) NoteImporter.__init__(self, col, file)
@ -122,17 +119,17 @@ class SupermemoXmlImporter(NoteImporter):
## TOOLS ## TOOLS
def _fudgeText(self, text: str) -> Any: def _fudgeText(self, text):
"Replace sm syntax to Anki syntax" "Replace sm syntax to Anki syntax"
text = text.replace("\n\r", "<br>") text = text.replace("\n\r", "<br>")
text = text.replace("\n", "<br>") text = text.replace("\n", "<br>")
return text return text
def _unicode2ascii(self,str: str) -> str: def _unicode2ascii(self,str):
"Remove diacritic punctuation from strings (titles)" "Remove diacritic punctuation from strings (titles)"
return "".join([ c for c in unicodedata.normalize('NFKD', str) if not unicodedata.combining(c)]) return "".join([ c for c in unicodedata.normalize('NFKD', str) if not unicodedata.combining(c)])
def _decode_htmlescapes(self,s: str) -> str: def _decode_htmlescapes(self,s):
"""Unescape HTML code.""" """Unescape HTML code."""
#In case of bad formated html you can import MinimalSoup etc.. see btflsoup source code #In case of bad formated html you can import MinimalSoup etc.. see btflsoup source code
from bs4 import BeautifulSoup as btflsoup from bs4 import BeautifulSoup as btflsoup
@ -145,7 +142,7 @@ class SupermemoXmlImporter(NoteImporter):
return str(btflsoup(s, "html.parser")) return str(btflsoup(s, "html.parser"))
def _afactor2efactor(self, af: float) -> Any: def _afactor2efactor(self, af):
# Adapted from <http://www.supermemo.com/beta/xml/xml-core.htm> # Adapted from <http://www.supermemo.com/beta/xml/xml-core.htm>
# Ranges for A-factors and E-factors # Ranges for A-factors and E-factors
@ -169,7 +166,7 @@ class SupermemoXmlImporter(NoteImporter):
## DEFAULT IMPORTER METHODS ## DEFAULT IMPORTER METHODS
def foreignNotes(self) -> List[ForeignNote]: def foreignNotes(self):
# Load file and parse it by minidom # Load file and parse it by minidom
self.loadSource(self.file) self.loadSource(self.file)
@ -185,12 +182,12 @@ class SupermemoXmlImporter(NoteImporter):
self.log.append(ngettext("%d card imported.", "%d cards imported.", self.total) % self.total) self.log.append(ngettext("%d card imported.", "%d cards imported.", self.total) % self.total)
return self.notes return self.notes
def fields(self) -> int: def fields(self):
return 2 return 2
## PARSER METHODS ## PARSER METHODS
def addItemToCards(self,item: SuperMemoElement) -> None: def addItemToCards(self,item):
"This method actually do conversion" "This method actually do conversion"
# new anki card # new anki card
@ -250,7 +247,7 @@ class SupermemoXmlImporter(NoteImporter):
self.notes.append(note) self.notes.append(note)
def logger(self,text: str,level: int = 1) -> None: def logger(self,text,level=1):
"Wrapper for Anki logger" "Wrapper for Anki logger"
dLevels={0:'',1:'Info',2:'Verbose',3:'Debug'} dLevels={0:'',1:'Info',2:'Verbose',3:'Debug'}
@ -262,7 +259,7 @@ class SupermemoXmlImporter(NoteImporter):
# OPEN AND LOAD # OPEN AND LOAD
def openAnything(self,source) -> Any: def openAnything(self,source):
"Open any source / actually only openig of files is used" "Open any source / actually only openig of files is used"
if source == "-": if source == "-":
@ -285,7 +282,7 @@ class SupermemoXmlImporter(NoteImporter):
import io import io
return io.StringIO(str(source)) return io.StringIO(str(source))
def loadSource(self, source: str) -> None: def loadSource(self, source):
"""Load source file and parse with xml.dom.minidom""" """Load source file and parse with xml.dom.minidom"""
self.source = source self.source = source
self.logger('Load started...') self.logger('Load started...')
@ -296,7 +293,7 @@ class SupermemoXmlImporter(NoteImporter):
# PARSE # PARSE
def parse(self, node: Optional[Any] = None) -> None: def parse(self, node=None):
"Parse method - parses document elements" "Parse method - parses document elements"
if node is None and self.xmldoc is not None: if node is None and self.xmldoc is not None:
@ -309,12 +306,12 @@ class SupermemoXmlImporter(NoteImporter):
else: else:
self.logger('No handler for method %s' % _method, level=3) self.logger('No handler for method %s' % _method, level=3)
def parse_Document(self, node) -> None: def parse_Document(self, node):
"Parse XML document" "Parse XML document"
self.parse(node.documentElement) self.parse(node.documentElement)
def parse_Element(self, node: Element) -> None: def parse_Element(self, node):
"Parse XML element" "Parse XML element"
_method = "do_%s" % node.tagName _method = "do_%s" % node.tagName
@ -325,7 +322,7 @@ class SupermemoXmlImporter(NoteImporter):
self.logger('No handler for method %s' % _method, level=3) self.logger('No handler for method %s' % _method, level=3)
#print traceback.print_exc() #print traceback.print_exc()
def parse_Text(self, node: Text) -> None: def parse_Text(self, node):
"Parse text inside elements. Text is stored into local buffer." "Parse text inside elements. Text is stored into local buffer."
text = node.data text = node.data
@ -339,12 +336,12 @@ class SupermemoXmlImporter(NoteImporter):
# DO # DO
def do_SuperMemoCollection(self, node: Element) -> None: def do_SuperMemoCollection(self, node):
"Process SM Collection" "Process SM Collection"
for child in node.childNodes: self.parse(child) for child in node.childNodes: self.parse(child)
def do_SuperMemoElement(self, node: Element) -> None: def do_SuperMemoElement(self, node):
"Process SM Element (Type - Title,Topics)" "Process SM Element (Type - Title,Topics)"
self.logger('='*45, level=3) self.logger('='*45, level=3)
@ -394,14 +391,14 @@ class SupermemoXmlImporter(NoteImporter):
t = self.cntMeta['title'].pop() t = self.cntMeta['title'].pop()
self.logger('End of topic \t- %s' % (t), level=2) self.logger('End of topic \t- %s' % (t), level=2)
def do_Content(self, node: Element) -> None: def do_Content(self, node):
"Process SM element Content" "Process SM element Content"
for child in node.childNodes: for child in node.childNodes:
if hasattr(child,'tagName') and child.firstChild is not None: if hasattr(child,'tagName') and child.firstChild is not None:
self.cntElm[-1][child.tagName]=child.firstChild.data self.cntElm[-1][child.tagName]=child.firstChild.data
def do_LearningData(self, node: Element) -> None: def do_LearningData(self, node):
"Process SM element LearningData" "Process SM element LearningData"
for child in node.childNodes: for child in node.childNodes:
@ -418,7 +415,7 @@ class SupermemoXmlImporter(NoteImporter):
# for child in node.childNodes: self.parse(child) # for child in node.childNodes: self.parse(child)
# self.cntElm[-1][node.tagName]=self.cntBuf.pop() # self.cntElm[-1][node.tagName]=self.cntBuf.pop()
def do_Title(self, node: Element) -> None: def do_Title(self, node):
"Process SM element Title" "Process SM element Title"
t = self._decode_htmlescapes(node.firstChild.data) t = self._decode_htmlescapes(node.firstChild.data)
@ -428,7 +425,7 @@ class SupermemoXmlImporter(NoteImporter):
self.logger('Start of topic \t- ' + " / ".join(self.cntMeta['title']), level=2) self.logger('Start of topic \t- ' + " / ".join(self.cntMeta['title']), level=2)
def do_Type(self, node: Element) -> None: def do_Type(self, node):
"Process SM element Type" "Process SM element Type"
if len(self.cntBuf) >=1 : if len(self.cntBuf) >=1 :

View file

@ -39,6 +39,8 @@ import inspect
from distutils.spawn import find_executable # pylint: disable=import-error,no-name-in-module from distutils.spawn import find_executable # pylint: disable=import-error,no-name-in-module
from queue import Queue, Empty, Full from queue import Queue, Empty, Full
from typing import Dict, Optional
class MPVError(Exception): class MPVError(Exception):
pass pass
@ -56,7 +58,6 @@ class MPVTimeoutError(MPVError):
pass pass
from anki.utils import isWin from anki.utils import isWin
from typing import Any
if isWin: if isWin:
# pylint: disable=import-error # pylint: disable=import-error
import win32file, win32pipe, pywintypes, winerror # pytype: disable=import-error import win32file, win32pipe, pywintypes, winerror # pytype: disable=import-error
@ -66,7 +67,7 @@ class MPVBase:
""" """
executable = find_executable("mpv") executable = find_executable("mpv")
popenEnv = None popenEnv: Optional[Dict[str,str]] = None
default_argv = [ default_argv = [
"--idle", "--idle",
@ -77,7 +78,7 @@ class MPVBase:
"--keep-open=no", "--keep-open=no",
] ]
def __init__(self, window_id=None, debug=False) -> None: def __init__(self, window_id=None, debug=False):
self.window_id = window_id self.window_id = window_id
self.debug = debug self.debug = debug
@ -88,18 +89,18 @@ class MPVBase:
self._prepare_thread() self._prepare_thread()
self._start_thread() self._start_thread()
def __del__(self) -> None: def __del__(self):
self._stop_thread() self._stop_thread()
self._stop_process() self._stop_process()
self._stop_socket() self._stop_socket()
def _thread_id(self) -> int: def _thread_id(self):
return threading.get_ident() return threading.get_ident()
# #
# Process # Process
# #
def _prepare_process(self) -> None: def _prepare_process(self):
"""Prepare the argument list for the mpv process. """Prepare the argument list for the mpv process.
""" """
self.argv = [self.executable] self.argv = [self.executable]
@ -108,12 +109,12 @@ class MPVBase:
if self.window_id is not None: if self.window_id is not None:
self.argv += ["--wid", str(self.window_id)] self.argv += ["--wid", str(self.window_id)]
def _start_process(self) -> None: def _start_process(self):
"""Start the mpv process. """Start the mpv process.
""" """
self._proc = subprocess.Popen(self.argv, env=self.popenEnv) self._proc = subprocess.Popen(self.argv, env=self.popenEnv)
def _stop_process(self) -> None: def _stop_process(self):
"""Stop the mpv process. """Stop the mpv process.
""" """
if hasattr(self, "_proc"): if hasattr(self, "_proc"):
@ -126,7 +127,7 @@ class MPVBase:
# #
# Socket communication # Socket communication
# #
def _prepare_socket(self) -> None: def _prepare_socket(self):
"""Create a random socket filename which we pass to mpv with the """Create a random socket filename which we pass to mpv with the
--input-unix-socket option. --input-unix-socket option.
""" """
@ -137,7 +138,7 @@ class MPVBase:
os.close(fd) os.close(fd)
os.remove(self._sock_filename) os.remove(self._sock_filename)
def _start_socket(self) -> None: def _start_socket(self):
"""Wait for the mpv process to create the unix socket and finish """Wait for the mpv process to create the unix socket and finish
startup. startup.
""" """
@ -174,7 +175,7 @@ class MPVBase:
else: else:
raise MPVProcessError("unable to start process") raise MPVProcessError("unable to start process")
def _stop_socket(self) -> None: def _stop_socket(self):
"""Clean up the socket. """Clean up the socket.
""" """
if hasattr(self, "_sock"): if hasattr(self, "_sock"):
@ -185,7 +186,7 @@ class MPVBase:
except OSError: except OSError:
pass pass
def _prepare_thread(self) -> None: def _prepare_thread(self):
"""Set up the queues for the communication threads. """Set up the queues for the communication threads.
""" """
self._request_queue = Queue(1) self._request_queue = Queue(1)
@ -193,14 +194,14 @@ class MPVBase:
self._event_queue = Queue() self._event_queue = Queue()
self._stop_event = threading.Event() self._stop_event = threading.Event()
def _start_thread(self) -> None: def _start_thread(self):
"""Start up the communication threads. """Start up the communication threads.
""" """
self._thread = threading.Thread(target=self._reader) self._thread = threading.Thread(target=self._reader)
self._thread.daemon = True self._thread.daemon = True
self._thread.start() self._thread.start()
def _stop_thread(self) -> None: def _stop_thread(self):
"""Stop the communication threads. """Stop the communication threads.
""" """
if hasattr(self, "_stop_event"): if hasattr(self, "_stop_event"):
@ -208,7 +209,7 @@ class MPVBase:
if hasattr(self, "_thread"): if hasattr(self, "_thread"):
self._thread.join() self._thread.join()
def _reader(self) -> None: def _reader(self):
"""Read the incoming json messages from the unix socket that is """Read the incoming json messages from the unix socket that is
connected to the mpv process. Pass them on to the message handler. connected to the mpv process. Pass them on to the message handler.
""" """
@ -250,21 +251,21 @@ class MPVBase:
# #
# Message handling # Message handling
# #
def _compose_message(self, message) -> bytes: def _compose_message(self, message):
"""Return a json representation from a message dictionary. """Return a json representation from a message dictionary.
""" """
# XXX may be strict is too strict ;-) # XXX may be strict is too strict ;-)
data = json.dumps(message) data = json.dumps(message)
return data.encode("utf8", "strict") + b"\n" return data.encode("utf8", "strict") + b"\n"
def _parse_message(self, data) -> Any: def _parse_message(self, data):
"""Return a message dictionary from a json representation. """Return a message dictionary from a json representation.
""" """
# XXX may be strict is too strict ;-) # XXX may be strict is too strict ;-)
data = data.decode("utf8", "strict") data = data.decode("utf8", "strict")
return json.loads(data) return json.loads(data)
def _handle_message(self, message) -> None: def _handle_message(self, message):
"""Handle different types of incoming messages, i.e. responses to """Handle different types of incoming messages, i.e. responses to
commands or asynchronous events. commands or asynchronous events.
""" """
@ -284,7 +285,7 @@ class MPVBase:
else: else:
raise MPVCommunicationError("invalid message %r" % message) raise MPVCommunicationError("invalid message %r" % message)
def _send_message(self, message, timeout=None) -> None: def _send_message(self, message, timeout=None):
"""Send a message/command to the mpv process, message must be a """Send a message/command to the mpv process, message must be a
dictionary of the form {"command": ["arg1", "arg2", ...]}. Responses dictionary of the form {"command": ["arg1", "arg2", ...]}. Responses
from the mpv process must be collected using _get_response(). from the mpv process must be collected using _get_response().
@ -321,7 +322,7 @@ class MPVBase:
raise MPVCommunicationError("broken sender socket") raise MPVCommunicationError("broken sender socket")
data = data[size:] data = data[size:]
def _get_response(self, timeout=None) -> Any: def _get_response(self, timeout=None):
"""Collect the response message to a previous request. If there was an """Collect the response message to a previous request. If there was an
error a MPVCommandError exception is raised, otherwise the command error a MPVCommandError exception is raised, otherwise the command
specific data is returned. specific data is returned.
@ -336,7 +337,7 @@ class MPVBase:
else: else:
return message.get("data") return message.get("data")
def _get_event(self, timeout=None) -> Any: def _get_event(self, timeout=None):
"""Collect a single event message that has been received out-of-band """Collect a single event message that has been received out-of-band
from the mpv process. If a timeout is specified and there have not from the mpv process. If a timeout is specified and there have not
been any events during that period, None is returned. been any events during that period, None is returned.
@ -346,7 +347,7 @@ class MPVBase:
except Empty: except Empty:
return None return None
def _send_request(self, message, timeout=None, _retry=1) -> Any: def _send_request(self, message, timeout=None, _retry=1):
"""Send a command to the mpv process and collect the result. """Send a command to the mpv process and collect the result.
""" """
self.ensure_running() self.ensure_running()
@ -366,12 +367,12 @@ class MPVBase:
# #
# Public API # Public API
# #
def is_running(self) -> bool: def is_running(self):
"""Return True if the mpv process is still active. """Return True if the mpv process is still active.
""" """
return self._proc.poll() is None return self._proc.poll() is None
def ensure_running(self) -> None: def ensure_running(self):
if not self.is_running(): if not self.is_running():
self._stop_thread() self._stop_thread()
self._stop_process() self._stop_process()
@ -383,7 +384,7 @@ class MPVBase:
self._prepare_thread() self._prepare_thread()
self._start_thread() self._start_thread()
def close(self) -> None: def close(self):
"""Shutdown the mpv process and our communication setup. """Shutdown the mpv process and our communication setup.
""" """
if self.is_running(): if self.is_running():
@ -414,7 +415,7 @@ class MPV(MPVBase):
threads to the same MPV instance are synchronized. threads to the same MPV instance are synchronized.
""" """
def __init__(self, *args, **kwargs) -> None: def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
self._callbacks = {} self._callbacks = {}
@ -464,7 +465,7 @@ class MPV(MPVBase):
# #
# Event/callback API # Event/callback API
# #
def _event_reader(self) -> None: def _event_reader(self):
"""Collect incoming event messages and call the event handler. """Collect incoming event messages and call the event handler.
""" """
while not self._stop_event.is_set(): while not self._stop_event.is_set():
@ -474,7 +475,7 @@ class MPV(MPVBase):
self._handle_event(message) self._handle_event(message)
def _handle_event(self, message) -> None: def _handle_event(self, message):
"""Lookup and call the callbacks for a particular event message. """Lookup and call the callbacks for a particular event message.
""" """
if message["event"] == "property-change": if message["event"] == "property-change":
@ -488,7 +489,7 @@ class MPV(MPVBase):
else: else:
callback() callback()
def register_callback(self, name, callback) -> None: def register_callback(self, name, callback):
"""Register a function `callback` for the event `name`. """Register a function `callback` for the event `name`.
""" """
try: try:
@ -498,7 +499,7 @@ class MPV(MPVBase):
self._callbacks.setdefault(name, []).append(callback) self._callbacks.setdefault(name, []).append(callback)
def unregister_callback(self, name, callback) -> None: def unregister_callback(self, name, callback):
"""Unregister a previously registered function `callback` for the event """Unregister a previously registered function `callback` for the event
`name`. `name`.
""" """
@ -512,7 +513,7 @@ class MPV(MPVBase):
except ValueError: except ValueError:
raise MPVError("callback %r not registered for event %r" % (callback, name)) raise MPVError("callback %r not registered for event %r" % (callback, name))
def register_property_callback(self, name, callback) -> int: def register_property_callback(self, name, callback):
"""Register a function `callback` for the property-change event on """Register a function `callback` for the property-change event on
property `name`. property `name`.
""" """
@ -534,7 +535,7 @@ class MPV(MPVBase):
self._property_serials[(name, callback)] = serial self._property_serials[(name, callback)] = serial
return serial return serial
def unregister_property_callback(self, name, callback) -> None: def unregister_property_callback(self, name, callback):
"""Unregister a previously registered function `callback` for the """Unregister a previously registered function `callback` for the
property-change event on property `name`. property-change event on property `name`.
""" """
@ -554,17 +555,17 @@ class MPV(MPVBase):
# #
# Public API # Public API
# #
def command(self, *args, timeout=1) -> Any: def command(self, *args, timeout=1):
"""Execute a single command on the mpv process and return the result. """Execute a single command on the mpv process and return the result.
""" """
return self._send_request({"command": list(args)}, timeout=timeout) return self._send_request({"command": list(args)}, timeout=timeout)
def get_property(self, name) -> Any: def get_property(self, name):
"""Return the value of property `name`. """Return the value of property `name`.
""" """
return self.command("get_property", name) return self.command("get_property", name)
def set_property(self, name, value) -> Any: def set_property(self, name, value):
"""Set the value of property `name`. """Set the value of property `name`.
""" """
return self.command("set_property", name, value) return self.command("set_property", name, value)

View file

@ -14,10 +14,6 @@ from anki.lang import _
from anki.consts import * from anki.consts import *
from anki.hooks import runHook from anki.hooks import runHook
from anki.cards import Card
#from anki.collection import _Collection
from typing import Any, Callable, Dict, List, Optional, Union, Tuple
# queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried # queue types: 0=new/cram, 1=lrn, 2=rev, 3=day lrn, -1=suspended, -2=buried
# revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram # revlog types: 0=lrn, 1=rev, 2=relrn, 3=cram
# positive revlog intervals are in days (rev), negative in seconds (lrn) # positive revlog intervals are in days (rev), negative in seconds (lrn)
@ -28,7 +24,7 @@ class Scheduler:
_spreadRev = True _spreadRev = True
_burySiblingsOnAnswer = True _burySiblingsOnAnswer = True
def __init__(self, col) -> None: def __init__(self, col):
self.col = col self.col = col
self.queueLimit = 50 self.queueLimit = 50
self.reportLimit = 1000 self.reportLimit = 1000
@ -37,7 +33,7 @@ class Scheduler:
self._haveQueues = False self._haveQueues = False
self._updateCutoff() self._updateCutoff()
def getCard(self) -> Any: def getCard(self):
"Pop the next card from the queue. None if finished." "Pop the next card from the queue. None if finished."
self._checkDay() self._checkDay()
if not self._haveQueues: if not self._haveQueues:
@ -51,14 +47,14 @@ class Scheduler:
card.startTimer() card.startTimer()
return card return card
def reset(self) -> None: def reset(self):
self._updateCutoff() self._updateCutoff()
self._resetLrn() self._resetLrn()
self._resetRev() self._resetRev()
self._resetNew() self._resetNew()
self._haveQueues = True self._haveQueues = True
def answerCard(self, card: Card, ease: int) -> None: def answerCard(self, card, ease):
self.col.log() self.col.log()
assert 1 <= ease <= 4 assert 1 <= ease <= 4
self.col.markReview(card) self.col.markReview(card)
@ -97,7 +93,7 @@ class Scheduler:
card.usn = self.col.usn() card.usn = self.col.usn()
card.flushSched() card.flushSched()
def counts(self, card: None = None) -> tuple: def counts(self, card=None):
counts = [self.newCount, self.lrnCount, self.revCount] counts = [self.newCount, self.lrnCount, self.revCount]
if card: if card:
idx = self.countIdx(card) idx = self.countIdx(card)
@ -107,7 +103,7 @@ class Scheduler:
counts[idx] += 1 counts[idx] += 1
return tuple(counts) return tuple(counts)
def dueForecast(self, days=7) -> List: def dueForecast(self, days=7):
"Return counts over next DAYS. Includes today." "Return counts over next DAYS. Includes today."
daysd = dict(self.col.db.all(""" daysd = dict(self.col.db.all("""
select due, count() from cards select due, count() from cards
@ -125,12 +121,12 @@ order by due""" % self._deckLimit(),
ret = [x[1] for x in sorted(daysd.items())] ret = [x[1] for x in sorted(daysd.items())]
return ret return ret
def countIdx(self, card: Card) -> Any: def countIdx(self, card):
if card.queue == 3: if card.queue == 3:
return 1 return 1
return card.queue return card.queue
def answerButtons(self, card: Card) -> int: def answerButtons(self, card):
if card.odue: if card.odue:
# normal review in dyn deck? # normal review in dyn deck?
if card.odid and card.queue == 2: if card.odid and card.queue == 2:
@ -144,7 +140,7 @@ order by due""" % self._deckLimit(),
else: else:
return 3 return 3
def unburyCards(self) -> None: def unburyCards(self):
"Unbury cards." "Unbury cards."
self.col.conf['lastUnburied'] = self.today self.col.conf['lastUnburied'] = self.today
self.col.log( self.col.log(
@ -152,7 +148,7 @@ order by due""" % self._deckLimit(),
self.col.db.execute( self.col.db.execute(
"update cards set queue=type where queue = -2") "update cards set queue=type where queue = -2")
def unburyCardsForDeck(self) -> None: def unburyCardsForDeck(self):
sids = ids2str(self.col.decks.active()) sids = ids2str(self.col.decks.active())
self.col.log( self.col.log(
self.col.db.list("select id from cards where queue = -2 and did in %s" self.col.db.list("select id from cards where queue = -2 and did in %s"
@ -164,7 +160,7 @@ order by due""" % self._deckLimit(),
# Rev/lrn/time daily stats # Rev/lrn/time daily stats
########################################################################## ##########################################################################
def _updateStats(self, card: Card, type: str, cnt: int = 1) -> None: def _updateStats(self, card, type, cnt=1):
key = type+"Today" key = type+"Today"
for g in ([self.col.decks.get(card.did)] + for g in ([self.col.decks.get(card.did)] +
self.col.decks.parents(card.did)): self.col.decks.parents(card.did)):
@ -172,7 +168,7 @@ order by due""" % self._deckLimit(),
g[key][1] += cnt g[key][1] += cnt
self.col.decks.save(g) self.col.decks.save(g)
def extendLimits(self, new, rev) -> None: def extendLimits(self, new, rev):
cur = self.col.decks.current() cur = self.col.decks.current()
parents = self.col.decks.parents(cur['id']) parents = self.col.decks.parents(cur['id'])
children = [self.col.decks.get(did) for (name, did) in children = [self.col.decks.get(did) for (name, did) in
@ -183,7 +179,7 @@ order by due""" % self._deckLimit(),
g['revToday'][1] -= rev g['revToday'][1] -= rev
self.col.decks.save(g) self.col.decks.save(g)
def _walkingCount(self, limFn: Optional[Callable] = None, cntFn: Optional[Callable] = None) -> Any: def _walkingCount(self, limFn=None, cntFn=None):
tot = 0 tot = 0
pcounts = {} pcounts = {}
# for each of the active decks # for each of the active decks
@ -217,7 +213,7 @@ order by due""" % self._deckLimit(),
# Deck list # Deck list
########################################################################## ##########################################################################
def deckDueList(self) -> List[list]: def deckDueList(self):
"Returns [deckname, did, rev, lrn, new]" "Returns [deckname, did, rev, lrn, new]"
self._checkDay() self._checkDay()
self.col.decks.checkIntegrity() self.col.decks.checkIntegrity()
@ -251,10 +247,10 @@ order by due""" % self._deckLimit(),
lims[deck['name']] = [nlim, rlim] lims[deck['name']] = [nlim, rlim]
return data return data
def deckDueTree(self) -> Any: def deckDueTree(self):
return self._groupChildren(self.deckDueList()) return self._groupChildren(self.deckDueList())
def _groupChildren(self, grps: List[List]) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]: def _groupChildren(self, grps):
# first, split the group names into components # first, split the group names into components
for g in grps: for g in grps:
g[0] = g[0].split("::") g[0] = g[0].split("::")
@ -263,7 +259,7 @@ order by due""" % self._deckLimit(),
# then run main function # then run main function
return self._groupChildrenMain(grps) return self._groupChildrenMain(grps)
def _groupChildrenMain(self, grps: List[List[Union[List[str], int]]]) -> Tuple[Tuple[Any, Any, Any, Any, Any, Any], ...]: def _groupChildrenMain(self, grps):
tree = [] tree = []
# group and recurse # group and recurse
def key(grp): def key(grp):
@ -304,7 +300,7 @@ order by due""" % self._deckLimit(),
# Getting the next card # Getting the next card
########################################################################## ##########################################################################
def _getCard(self) -> Any: def _getCard(self):
"Return the next due card id, or None." "Return the next due card id, or None."
# learning card due? # learning card due?
c = self._getLrnCard() c = self._getLrnCard()
@ -333,19 +329,19 @@ order by due""" % self._deckLimit(),
# New cards # New cards
########################################################################## ##########################################################################
def _resetNewCount(self) -> None: def _resetNewCount(self):
cntFn = lambda did, lim: self.col.db.scalar(""" cntFn = lambda did, lim: self.col.db.scalar("""
select count() from (select 1 from cards where select count() from (select 1 from cards where
did = ? and queue = 0 limit ?)""", did, lim) did = ? and queue = 0 limit ?)""", did, lim)
self.newCount = self._walkingCount(self._deckNewLimitSingle, cntFn) self.newCount = self._walkingCount(self._deckNewLimitSingle, cntFn)
def _resetNew(self) -> None: def _resetNew(self):
self._resetNewCount() self._resetNewCount()
self._newDids = self.col.decks.active()[:] self._newDids = self.col.decks.active()[:]
self._newQueue = [] self._newQueue = []
self._updateNewCardRatio() self._updateNewCardRatio()
def _fillNew(self) -> Any: def _fillNew(self):
if self._newQueue: if self._newQueue:
return True return True
if not self.newCount: if not self.newCount:
@ -369,12 +365,12 @@ did = ? and queue = 0 limit ?)""", did, lim)
self._resetNew() self._resetNew()
return self._fillNew() return self._fillNew()
def _getNewCard(self) -> Any: def _getNewCard(self):
if self._fillNew(): if self._fillNew():
self.newCount -= 1 self.newCount -= 1
return self.col.getCard(self._newQueue.pop()) return self.col.getCard(self._newQueue.pop())
def _updateNewCardRatio(self) -> None: def _updateNewCardRatio(self):
if self.col.conf['newSpread'] == NEW_CARDS_DISTRIBUTE: if self.col.conf['newSpread'] == NEW_CARDS_DISTRIBUTE:
if self.newCount: if self.newCount:
self.newCardModulus = ( self.newCardModulus = (
@ -385,7 +381,7 @@ did = ? and queue = 0 limit ?)""", did, lim)
return return
self.newCardModulus = 0 self.newCardModulus = 0
def _timeForNewCard(self) -> Optional[int]: def _timeForNewCard(self):
"True if it's time to display a new card when distributing." "True if it's time to display a new card when distributing."
if not self.newCount: if not self.newCount:
return False return False
@ -396,7 +392,7 @@ did = ? and queue = 0 limit ?)""", did, lim)
elif self.newCardModulus: elif self.newCardModulus:
return self.reps and self.reps % self.newCardModulus == 0 return self.reps and self.reps % self.newCardModulus == 0
def _deckNewLimit(self, did: int, fn: Optional[Callable] = None) -> Any: def _deckNewLimit(self, did, fn=None):
if not fn: if not fn:
fn = self._deckNewLimitSingle fn = self._deckNewLimitSingle
sel = self.col.decks.get(did) sel = self.col.decks.get(did)
@ -410,7 +406,7 @@ did = ? and queue = 0 limit ?)""", did, lim)
lim = min(rem, lim) lim = min(rem, lim)
return lim return lim
def _newForDeck(self, did: int, lim: int) -> Any: def _newForDeck(self, did, lim):
"New count for a single deck." "New count for a single deck."
if not lim: if not lim:
return 0 return 0
@ -419,14 +415,14 @@ did = ? and queue = 0 limit ?)""", did, lim)
select count() from select count() from
(select 1 from cards where did = ? and queue = 0 limit ?)""", did, lim) (select 1 from cards where did = ? and queue = 0 limit ?)""", did, lim)
def _deckNewLimitSingle(self, g: Dict[str, Any]) -> Any: def _deckNewLimitSingle(self, g):
"Limit for deck without parent limits." "Limit for deck without parent limits."
if g['dyn']: if g['dyn']:
return self.reportLimit return self.reportLimit
c = self.col.decks.confForDid(g['id']) c = self.col.decks.confForDid(g['id'])
return max(0, c['new']['perDay'] - g['newToday'][1]) return max(0, c['new']['perDay'] - g['newToday'][1])
def totalNewForCurrentDeck(self) -> Any: def totalNewForCurrentDeck(self):
return self.col.db.scalar( return self.col.db.scalar(
""" """
select count() from cards where id in ( select count() from cards where id in (
@ -436,7 +432,7 @@ select id from cards where did in %s and queue = 0 limit ?)"""
# Learning queues # Learning queues
########################################################################## ##########################################################################
def _resetLrnCount(self) -> None: def _resetLrnCount(self):
# sub-day # sub-day
self.lrnCount = self.col.db.scalar(""" self.lrnCount = self.col.db.scalar("""
select sum(left/1000) from (select left from cards where select sum(left/1000) from (select left from cards where
@ -449,14 +445,14 @@ select count() from cards where did in %s and queue = 3
and due <= ? limit %d""" % (self._deckLimit(), self.reportLimit), and due <= ? limit %d""" % (self._deckLimit(), self.reportLimit),
self.today) self.today)
def _resetLrn(self) -> None: def _resetLrn(self):
self._resetLrnCount() self._resetLrnCount()
self._lrnQueue = [] self._lrnQueue = []
self._lrnDayQueue = [] self._lrnDayQueue = []
self._lrnDids = self.col.decks.active()[:] self._lrnDids = self.col.decks.active()[:]
# sub-day learning # sub-day learning
def _fillLrn(self) -> Any: def _fillLrn(self):
if not self.lrnCount: if not self.lrnCount:
return False return False
if self._lrnQueue: if self._lrnQueue:
@ -469,7 +465,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
self._lrnQueue.sort() self._lrnQueue.sort()
return self._lrnQueue return self._lrnQueue
def _getLrnCard(self, collapse: bool = False) -> Any: def _getLrnCard(self, collapse=False):
if self._fillLrn(): if self._fillLrn():
cutoff = time.time() cutoff = time.time()
if collapse: if collapse:
@ -481,7 +477,7 @@ limit %d""" % (self._deckLimit(), self.reportLimit), lim=self.dayCutoff)
return card return card
# daily learning # daily learning
def _fillLrnDay(self) -> Optional[bool]: def _fillLrnDay(self):
if not self.lrnCount: if not self.lrnCount:
return False return False
if self._lrnDayQueue: if self._lrnDayQueue:
@ -505,15 +501,15 @@ did = ? and queue = 3 and due <= ? limit ?""",
# nothing left in the deck; move to next # nothing left in the deck; move to next
self._lrnDids.pop(0) self._lrnDids.pop(0)
def _getLrnDayCard(self) -> Any: def _getLrnDayCard(self):
if self._fillLrnDay(): if self._fillLrnDay():
self.lrnCount -= 1 self.lrnCount -= 1
return self.col.getCard(self._lrnDayQueue.pop()) return self.col.getCard(self._lrnDayQueue.pop())
def _answerLrnCard(self, card: Card, ease: int) -> None: def _answerLrnCard(self, card, ease):
# ease 1=no, 2=yes, 3=remove # ease 1=no, 2=yes, 3=remove
conf = self._lrnConf(card) conf = self._lrnConf(card)
if card.odid and not card.wasNew: # type: ignore if card.odid and not card.wasNew:
type = 3 type = 3
elif card.type == 2: elif card.type == 2:
type = 2 type = 2
@ -572,7 +568,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
card.queue = 3 card.queue = 3
self._logLrn(card, ease, conf, leaving, type, lastLeft) self._logLrn(card, ease, conf, leaving, type, lastLeft)
def _delayForGrade(self, conf: Dict[str, Any], left: int) -> Any: def _delayForGrade(self, conf, left):
left = left % 1000 left = left % 1000
try: try:
delay = conf['delays'][-left] delay = conf['delays'][-left]
@ -584,13 +580,13 @@ did = ? and queue = 3 and due <= ? limit ?""",
delay = 1 delay = 1
return delay*60 return delay*60
def _lrnConf(self, card: Card) -> Any: def _lrnConf(self, card):
if card.type == 2: if card.type == 2:
return self._lapseConf(card) return self._lapseConf(card)
else: else:
return self._newConf(card) return self._newConf(card)
def _rescheduleAsRev(self, card: Card, conf: Dict[str, Any], early: bool) -> None: def _rescheduleAsRev(self, card, conf, early):
lapse = card.type == 2 lapse = card.type == 2
if lapse: if lapse:
if self._resched(card): if self._resched(card):
@ -613,7 +609,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
card.queue = card.type = 0 card.queue = card.type = 0
card.due = self.col.nextID("pos") card.due = self.col.nextID("pos")
def _startingLeft(self, card: Card) -> int: def _startingLeft(self, card):
if card.type == 2: if card.type == 2:
conf = self._lapseConf(card) conf = self._lapseConf(card)
else: else:
@ -622,7 +618,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
tod = self._leftToday(conf['delays'], tot) tod = self._leftToday(conf['delays'], tot)
return tot + tod*1000 return tot + tod*1000
def _leftToday(self, delays: Any, left: int, now: None = None) -> int: def _leftToday(self, delays, left, now=None):
"The number of steps that can be completed by the day cutoff." "The number of steps that can be completed by the day cutoff."
if not now: if not now:
now = intTime() now = intTime()
@ -635,7 +631,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
ok = i ok = i
return ok+1 return ok+1
def _graduatingIvl(self, card: Card, conf: Dict[str, Any], early: bool, adj: bool = True) -> Any: def _graduatingIvl(self, card, conf, early, adj=True):
if card.type == 2: if card.type == 2:
# lapsed card being relearnt # lapsed card being relearnt
if card.odid: if card.odid:
@ -653,13 +649,13 @@ did = ? and queue = 3 and due <= ? limit ?""",
else: else:
return ideal return ideal
def _rescheduleNew(self, card: Card, conf: Dict[str, Any], early: bool) -> None: def _rescheduleNew(self, card, conf, early):
"Reschedule a new card that's graduated for the first time." "Reschedule a new card that's graduated for the first time."
card.ivl = self._graduatingIvl(card, conf, early) card.ivl = self._graduatingIvl(card, conf, early)
card.due = self.today+card.ivl card.due = self.today+card.ivl
card.factor = conf['initialFactor'] card.factor = conf['initialFactor']
def _logLrn(self, card: Card, ease: int, conf: Dict[str, Any], leaving: bool, type: int, lastLeft: int) -> None: def _logLrn(self, card, ease, conf, leaving, type, lastLeft):
lastIvl = -(self._delayForGrade(conf, lastLeft)) lastIvl = -(self._delayForGrade(conf, lastLeft))
ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.left)) ivl = card.ivl if leaving else -(self._delayForGrade(conf, card.left))
def log(): def log():
@ -674,7 +670,7 @@ did = ? and queue = 3 and due <= ? limit ?""",
time.sleep(0.01) time.sleep(0.01)
log() log()
def removeLrn(self, ids: Optional[List[int]] = None) -> None: def removeLrn(self, ids=None):
"Remove cards from the learning queues." "Remove cards from the learning queues."
if ids: if ids:
extra = " and id in "+ids2str(ids) extra = " and id in "+ids2str(ids)
@ -693,7 +689,7 @@ where queue in (1,3) and type = 2
self.forgetCards(self.col.db.list( self.forgetCards(self.col.db.list(
"select id from cards where queue in (1,3) %s" % extra)) "select id from cards where queue in (1,3) %s" % extra))
def _lrnForDeck(self, did: int) -> Any: def _lrnForDeck(self, did):
cnt = self.col.db.scalar( cnt = self.col.db.scalar(
""" """
select sum(left/1000) from select sum(left/1000) from
@ -709,16 +705,16 @@ and due <= ? limit ?)""",
# Reviews # Reviews
########################################################################## ##########################################################################
def _deckRevLimit(self, did: int) -> Any: def _deckRevLimit(self, did):
return self._deckNewLimit(did, self._deckRevLimitSingle) return self._deckNewLimit(did, self._deckRevLimitSingle)
def _deckRevLimitSingle(self, d: Dict[str, Any]) -> Any: def _deckRevLimitSingle(self, d):
if d['dyn']: if d['dyn']:
return self.reportLimit return self.reportLimit
c = self.col.decks.confForDid(d['id']) c = self.col.decks.confForDid(d['id'])
return max(0, c['rev']['perDay'] - d['revToday'][1]) return max(0, c['rev']['perDay'] - d['revToday'][1])
def _revForDeck(self, did: int, lim: int) -> Any: def _revForDeck(self, did, lim):
lim = min(lim, self.reportLimit) lim = min(lim, self.reportLimit)
return self.col.db.scalar( return self.col.db.scalar(
""" """
@ -727,7 +723,7 @@ select count() from
and due <= ? limit ?)""", and due <= ? limit ?)""",
did, self.today, lim) did, self.today, lim)
def _resetRevCount(self) -> None: def _resetRevCount(self):
def cntFn(did, lim): def cntFn(did, lim):
return self.col.db.scalar(""" return self.col.db.scalar("""
select count() from (select id from cards where select count() from (select id from cards where
@ -736,12 +732,12 @@ did = ? and queue = 2 and due <= ? limit %d)""" % lim,
self.revCount = self._walkingCount( self.revCount = self._walkingCount(
self._deckRevLimitSingle, cntFn) self._deckRevLimitSingle, cntFn)
def _resetRev(self) -> None: def _resetRev(self):
self._resetRevCount() self._resetRevCount()
self._revQueue = [] self._revQueue = []
self._revDids = self.col.decks.active()[:] self._revDids = self.col.decks.active()[:]
def _fillRev(self) -> Any: def _fillRev(self):
if self._revQueue: if self._revQueue:
return True return True
if not self.revCount: if not self.revCount:
@ -778,12 +774,12 @@ did = ? and queue = 2 and due <= ? limit ?""",
self._resetRev() self._resetRev()
return self._fillRev() return self._fillRev()
def _getRevCard(self) -> Any: def _getRevCard(self):
if self._fillRev(): if self._fillRev():
self.revCount -= 1 self.revCount -= 1
return self.col.getCard(self._revQueue.pop()) return self.col.getCard(self._revQueue.pop())
def totalRevForCurrentDeck(self) -> Any: def totalRevForCurrentDeck(self):
return self.col.db.scalar( return self.col.db.scalar(
""" """
select count() from cards where id in ( select count() from cards where id in (
@ -793,7 +789,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# Answering a review card # Answering a review card
########################################################################## ##########################################################################
def _answerRevCard(self, card: Card, ease: int) -> None: def _answerRevCard(self, card, ease):
delay = 0 delay = 0
if ease == 1: if ease == 1:
delay = self._rescheduleLapse(card) delay = self._rescheduleLapse(card)
@ -801,7 +797,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self._rescheduleRev(card, ease) self._rescheduleRev(card, ease)
self._logRev(card, ease, delay) self._logRev(card, ease, delay)
def _rescheduleLapse(self, card: Card) -> Any: def _rescheduleLapse(self, card):
conf = self._lapseConf(card) conf = self._lapseConf(card)
card.lastIvl = card.ivl card.lastIvl = card.ivl
if self._resched(card): if self._resched(card):
@ -837,10 +833,10 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
card.queue = 3 card.queue = 3
return delay return delay
def _nextLapseIvl(self, card: Card, conf: Dict[str, Any]) -> Any: def _nextLapseIvl(self, card, conf):
return max(conf['minInt'], int(card.ivl*conf['mult'])) return max(conf['minInt'], int(card.ivl*conf['mult']))
def _rescheduleRev(self, card: Card, ease: int) -> None: def _rescheduleRev(self, card, ease):
# update interval # update interval
card.lastIvl = card.ivl card.lastIvl = card.ivl
if self._resched(card): if self._resched(card):
@ -855,7 +851,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
card.odid = 0 card.odid = 0
card.odue = 0 card.odue = 0
def _logRev(self, card: Card, ease: int, delay: Union[int, float]) -> None: def _logRev(self, card, ease, delay):
def log(): def log():
self.col.db.execute( self.col.db.execute(
"insert into revlog values (?,?,?,?,?,?,?,?,?)", "insert into revlog values (?,?,?,?,?,?,?,?,?)",
@ -872,7 +868,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# Interval management # Interval management
########################################################################## ##########################################################################
def _nextRevIvl(self, card: Card, ease: int) -> Any: def _nextRevIvl(self, card, ease):
"Ideal next interval for CARD, given EASE." "Ideal next interval for CARD, given EASE."
delay = self._daysLate(card) delay = self._daysLate(card)
conf = self._revConf(card) conf = self._revConf(card)
@ -890,11 +886,11 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# interval capped? # interval capped?
return min(interval, conf['maxIvl']) return min(interval, conf['maxIvl'])
def _fuzzedIvl(self, ivl: int) -> int: def _fuzzedIvl(self, ivl):
min, max = self._fuzzIvlRange(ivl) min, max = self._fuzzIvlRange(ivl)
return random.randint(min, max) return random.randint(min, max)
def _fuzzIvlRange(self, ivl: int) -> List: def _fuzzIvlRange(self, ivl):
if ivl < 2: if ivl < 2:
return [1, 1] return [1, 1]
elif ivl == 2: elif ivl == 2:
@ -909,22 +905,22 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
fuzz = max(fuzz, 1) fuzz = max(fuzz, 1)
return [ivl-fuzz, ivl+fuzz] return [ivl-fuzz, ivl+fuzz]
def _constrainedIvl(self, ivl: float, conf: Dict[str, Any], prev: int) -> int: def _constrainedIvl(self, ivl, conf, prev):
"Integer interval after interval factor and prev+1 constraints applied." "Integer interval after interval factor and prev+1 constraints applied."
new = ivl * conf.get('ivlFct', 1) new = ivl * conf.get('ivlFct', 1)
return int(max(new, prev+1)) return int(max(new, prev+1))
def _daysLate(self, card: Card) -> Any: def _daysLate(self, card):
"Number of days later than scheduled." "Number of days later than scheduled."
due = card.odue if card.odid else card.due due = card.odue if card.odid else card.due
return max(0, self.today - due) return max(0, self.today - due)
def _updateRevIvl(self, card: Card, ease: int) -> None: def _updateRevIvl(self, card, ease):
idealIvl = self._nextRevIvl(card, ease) idealIvl = self._nextRevIvl(card, ease)
card.ivl = min(max(self._adjRevIvl(card, idealIvl), card.ivl+1), card.ivl = min(max(self._adjRevIvl(card, idealIvl), card.ivl+1),
self._revConf(card)['maxIvl']) self._revConf(card)['maxIvl'])
def _adjRevIvl(self, card: Card, idealIvl: int) -> int: def _adjRevIvl(self, card, idealIvl):
if self._spreadRev: if self._spreadRev:
idealIvl = self._fuzzedIvl(idealIvl) idealIvl = self._fuzzedIvl(idealIvl)
return idealIvl return idealIvl
@ -932,7 +928,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
# Dynamic deck handling # Dynamic deck handling
########################################################################## ##########################################################################
def rebuildDyn(self, did: Optional[int] = None) -> Any: def rebuildDyn(self, did=None):
"Rebuild a dynamic deck." "Rebuild a dynamic deck."
did = did or self.col.decks.selected() did = did or self.col.decks.selected()
deck = self.col.decks.get(did) deck = self.col.decks.get(did)
@ -946,7 +942,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self.col.decks.select(did) self.col.decks.select(did)
return ids return ids
def _fillDyn(self, deck: Dict[str, Any]) -> Any: def _fillDyn(self, deck):
search, limit, order = deck['terms'][0] search, limit, order = deck['terms'][0]
orderlimit = self._dynOrder(order, limit) orderlimit = self._dynOrder(order, limit)
if search.strip(): if search.strip():
@ -962,7 +958,7 @@ select id from cards where did in %s and queue = 2 and due <= ? limit ?)"""
self._moveToDyn(deck['id'], ids) self._moveToDyn(deck['id'], ids)
return ids return ids
def emptyDyn(self, did: Optional[int], lim: Optional[str] = None) -> None: def emptyDyn(self, did, lim=None):
if not lim: if not lim:
lim = "did = %s" % did lim = "did = %s" % did
self.col.log(self.col.db.list("select id from cards where %s" % lim)) self.col.log(self.col.db.list("select id from cards where %s" % lim))
@ -973,10 +969,10 @@ else type end), type = (case when type = 1 then 0 else type end),
due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim, due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim,
self.col.usn()) self.col.usn())
def remFromDyn(self, cids: List[int]) -> None: def remFromDyn(self, cids):
self.emptyDyn(None, "id in %s and odid" % ids2str(cids)) self.emptyDyn(None, "id in %s and odid" % ids2str(cids))
def _dynOrder(self, o: int, l: int) -> str: def _dynOrder(self, o, l):
if o == DYN_OLDEST: if o == DYN_OLDEST:
t = "(select max(id) from revlog where cid=c.id)" t = "(select max(id) from revlog where cid=c.id)"
elif o == DYN_RANDOM: elif o == DYN_RANDOM:
@ -1001,7 +997,7 @@ due = odue, odue = 0, odid = 0, usn = ? where %s""" % lim,
t = "c.due" t = "c.due"
return t + " limit %d" % l return t + " limit %d" % l
def _moveToDyn(self, did: int, ids: List[int]) -> None: def _moveToDyn(self, did, ids):
deck = self.col.decks.get(did) deck = self.col.decks.get(did)
data = [] data = []
t = intTime(); u = self.col.usn() t = intTime(); u = self.col.usn()
@ -1020,7 +1016,7 @@ odid = (case when odid then odid else did end),
odue = (case when odue then odue else due end), odue = (case when odue then odue else due end),
did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data) did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
def _dynIvlBoost(self, card: Card) -> Any: def _dynIvlBoost(self, card):
assert card.odid and card.type == 2 assert card.odid and card.type == 2
assert card.factor assert card.factor
elapsed = card.ivl - (card.odue - self.today) elapsed = card.ivl - (card.odue - self.today)
@ -1032,7 +1028,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
# Leeches # Leeches
########################################################################## ##########################################################################
def _checkLeech(self, card: Card, conf: Dict[str, Any]) -> Optional[bool]: def _checkLeech(self, card, conf):
"Leech handler. True if card was a leech." "Leech handler. True if card was a leech."
lf = conf['leechFails'] lf = conf['leechFails']
if not lf: if not lf:
@ -1061,10 +1057,10 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
# Tools # Tools
########################################################################## ##########################################################################
def _cardConf(self, card: Card) -> Any: def _cardConf(self, card):
return self.col.decks.confForDid(card.did) return self.col.decks.confForDid(card.did)
def _newConf(self, card: Card) -> Any: def _newConf(self, card):
conf = self._cardConf(card) conf = self._cardConf(card)
# normal deck # normal deck
if not card.odid: if not card.odid:
@ -1084,7 +1080,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
perDay=self.reportLimit perDay=self.reportLimit
) )
def _lapseConf(self, card: Card) -> Any: def _lapseConf(self, card):
conf = self._cardConf(card) conf = self._cardConf(card)
# normal deck # normal deck
if not card.odid: if not card.odid:
@ -1103,7 +1099,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
resched=conf['resched'], resched=conf['resched'],
) )
def _revConf(self, card: Card) -> Any: def _revConf(self, card):
conf = self._cardConf(card) conf = self._cardConf(card)
# normal deck # normal deck
if not card.odid: if not card.odid:
@ -1111,10 +1107,10 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
# dynamic deck # dynamic deck
return self.col.decks.confForDid(card.odid)['rev'] return self.col.decks.confForDid(card.odid)['rev']
def _deckLimit(self) -> str: def _deckLimit(self):
return ids2str(self.col.decks.active()) return ids2str(self.col.decks.active())
def _resched(self, card: Card) -> Any: def _resched(self, card):
conf = self._cardConf(card) conf = self._cardConf(card)
if not conf['dyn']: if not conf['dyn']:
return True return True
@ -1123,7 +1119,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
# Daily cutoff # Daily cutoff
########################################################################## ##########################################################################
def _updateCutoff(self) -> None: def _updateCutoff(self):
oldToday = self.today oldToday = self.today
# days since col created # days since col created
self.today = int((time.time() - self.col.crt) // 86400) self.today = int((time.time() - self.col.crt) // 86400)
@ -1145,7 +1141,7 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
if unburied < self.today: if unburied < self.today:
self.unburyCards() self.unburyCards()
def _checkDay(self) -> None: def _checkDay(self):
# check if the day has rolled over # check if the day has rolled over
if time.time() > self.dayCutoff: if time.time() > self.dayCutoff:
self.reset() self.reset()
@ -1153,12 +1149,12 @@ did = ?, queue = %s, due = ?, usn = ? where id = ?""" % queue, data)
# Deck finished state # Deck finished state
########################################################################## ##########################################################################
def finishedMsg(self) -> str: def finishedMsg(self):
return ("<b>"+_( return ("<b>"+_(
"Congratulations! You have finished this deck for now.")+ "Congratulations! You have finished this deck for now.")+
"</b><br><br>" + self._nextDueMsg()) "</b><br><br>" + self._nextDueMsg())
def _nextDueMsg(self) -> str: def _nextDueMsg(self):
line = [] line = []
# the new line replacements are so we don't break translations # the new line replacements are so we don't break translations
# in a point release # in a point release
@ -1185,20 +1181,20 @@ Some related or buried cards were delayed until a later session.""")+now)
To study outside of the normal schedule, click the Custom Study button below.""")) To study outside of the normal schedule, click the Custom Study button below."""))
return "<p>".join(line) return "<p>".join(line)
def revDue(self) -> Any: def revDue(self):
"True if there are any rev cards due." "True if there are any rev cards due."
return self.col.db.scalar( return self.col.db.scalar(
("select 1 from cards where did in %s and queue = 2 " ("select 1 from cards where did in %s and queue = 2 "
"and due <= ? limit 1") % self._deckLimit(), "and due <= ? limit 1") % self._deckLimit(),
self.today) self.today)
def newDue(self) -> Any: def newDue(self):
"True if there are any new cards due." "True if there are any new cards due."
return self.col.db.scalar( return self.col.db.scalar(
("select 1 from cards where did in %s and queue = 0 " ("select 1 from cards where did in %s and queue = 0 "
"limit 1") % self._deckLimit()) "limit 1") % self._deckLimit())
def haveBuried(self) -> bool: def haveBuried(self):
sdids = ids2str(self.col.decks.active()) sdids = ids2str(self.col.decks.active())
cnt = self.col.db.scalar( cnt = self.col.db.scalar(
"select 1 from cards where queue = -2 and did in %s limit 1" % sdids) "select 1 from cards where queue = -2 and did in %s limit 1" % sdids)
@ -1207,7 +1203,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
# Next time reports # Next time reports
########################################################################## ##########################################################################
def nextIvlStr(self, card: Card, ease: int, short: bool = False) -> Any: def nextIvlStr(self, card, ease, short=False):
"Return the next interval for CARD as a string." "Return the next interval for CARD as a string."
ivl = self.nextIvl(card, ease) ivl = self.nextIvl(card, ease)
if not ivl: if not ivl:
@ -1217,7 +1213,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
s = "<"+s s = "<"+s
return s return s
def nextIvl(self, card: Card, ease: int) -> Any: def nextIvl(self, card, ease):
"Return the next interval for CARD, in seconds." "Return the next interval for CARD, in seconds."
if card.queue in (0,1,3): if card.queue in (0,1,3):
return self._nextLrnIvl(card, ease) return self._nextLrnIvl(card, ease)
@ -1232,7 +1228,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
return self._nextRevIvl(card, ease)*86400 return self._nextRevIvl(card, ease)*86400
# this isn't easily extracted from the learn code # this isn't easily extracted from the learn code
def _nextLrnIvl(self, card: Card, ease: int) -> Any: def _nextLrnIvl(self, card, ease):
if card.queue == 0: if card.queue == 0:
card.left = self._startingLeft(card) card.left = self._startingLeft(card)
conf = self._lrnConf(card) conf = self._lrnConf(card)
@ -1257,7 +1253,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
# Suspending # Suspending
########################################################################## ##########################################################################
def suspendCards(self, ids: List[int]) -> None: def suspendCards(self, ids):
"Suspend cards." "Suspend cards."
self.col.log(ids) self.col.log(ids)
self.remFromDyn(ids) self.remFromDyn(ids)
@ -1266,7 +1262,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
"update cards set queue=-1,mod=?,usn=? where id in "+ "update cards set queue=-1,mod=?,usn=? where id in "+
ids2str(ids), intTime(), self.col.usn()) ids2str(ids), intTime(), self.col.usn())
def unsuspendCards(self, ids: List[int]) -> None: def unsuspendCards(self, ids):
"Unsuspend cards." "Unsuspend cards."
self.col.log(ids) self.col.log(ids)
self.col.db.execute( self.col.db.execute(
@ -1274,7 +1270,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
"where queue = -1 and id in "+ ids2str(ids), "where queue = -1 and id in "+ ids2str(ids),
intTime(), self.col.usn()) intTime(), self.col.usn())
def buryCards(self, cids: List[int]) -> None: def buryCards(self, cids):
self.col.log(cids) self.col.log(cids)
self.remFromDyn(cids) self.remFromDyn(cids)
self.removeLrn(cids) self.removeLrn(cids)
@ -1282,7 +1278,7 @@ To study outside of the normal schedule, click the Custom Study button below."""
update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids), update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
intTime(), self.col.usn()) intTime(), self.col.usn())
def buryNote(self, nid: int) -> None: def buryNote(self, nid):
"Bury all cards for note until next session." "Bury all cards for note until next session."
cids = self.col.db.list( cids = self.col.db.list(
"select id from cards where nid = ? and queue >= 0", nid) "select id from cards where nid = ? and queue >= 0", nid)
@ -1291,7 +1287,7 @@ update cards set queue=-2,mod=?,usn=? where id in """+ids2str(cids),
# Sibling spacing # Sibling spacing
########################################################################## ##########################################################################
def _burySiblings(self, card: Card) -> None: def _burySiblings(self, card):
toBury = [] toBury = []
nconf = self._newConf(card) nconf = self._newConf(card)
buryNew = nconf.get("bury", True) buryNew = nconf.get("bury", True)
@ -1328,7 +1324,7 @@ and (queue=0 or (queue=2 and due<=?))""",
# Resetting # Resetting
########################################################################## ##########################################################################
def forgetCards(self, ids: List[int]) -> None: def forgetCards(self, ids):
"Put cards at the end of the new queue." "Put cards at the end of the new queue."
self.remFromDyn(ids) self.remFromDyn(ids)
self.col.db.execute( self.col.db.execute(
@ -1340,7 +1336,7 @@ and (queue=0 or (queue=2 and due<=?))""",
self.sortCards(ids, start=pmax+1) self.sortCards(ids, start=pmax+1)
self.col.log(ids) self.col.log(ids)
def reschedCards(self, ids: List[int], imin: int, imax: int) -> None: def reschedCards(self, ids, imin, imax):
"Put cards in review queue with a new interval in days (min, max)." "Put cards in review queue with a new interval in days (min, max)."
d = [] d = []
t = self.today t = self.today
@ -1356,7 +1352,7 @@ usn=:usn,mod=:mod,factor=:fact where id=:id""",
d) d)
self.col.log(ids) self.col.log(ids)
def resetCards(self, ids) -> None: def resetCards(self, ids):
"Completely reset cards for export." "Completely reset cards for export."
sids = ids2str(ids) sids = ids2str(ids)
# we want to avoid resetting due number of existing new cards on export # we want to avoid resetting due number of existing new cards on export
@ -1375,7 +1371,7 @@ usn=:usn,mod=:mod,factor=:fact where id=:id""",
# Repositioning new cards # Repositioning new cards
########################################################################## ##########################################################################
def sortCards(self, cids: List[int], start: int = 1, step: int = 1, shuffle: bool = False, shift: bool = False) -> None: def sortCards(self, cids, start=1, step=1, shuffle=False, shift=False):
scids = ids2str(cids) scids = ids2str(cids)
now = intTime() now = intTime()
nids = [] nids = []
@ -1415,15 +1411,15 @@ and due >= ? and queue = 0""" % scids, now, self.col.usn(), shiftby, low)
self.col.db.executemany( self.col.db.executemany(
"update cards set due=:due,mod=:now,usn=:usn where id = :cid", d) "update cards set due=:due,mod=:now,usn=:usn where id = :cid", d)
def randomizeCards(self, did: int) -> None: def randomizeCards(self, did):
cids = self.col.db.list("select id from cards where did = ?", did) cids = self.col.db.list("select id from cards where did = ?", did)
self.sortCards(cids, shuffle=True) self.sortCards(cids, shuffle=True)
def orderCards(self, did: int) -> None: def orderCards(self, did):
cids = self.col.db.list("select id from cards where did = ? order by id", did) cids = self.col.db.list("select id from cards where did = ? order by id", did)
self.sortCards(cids) self.sortCards(cids)
def resortConf(self, conf) -> None: def resortConf(self, conf):
for did in self.col.decks.didsForConf(conf): for did in self.col.decks.didsForConf(conf):
if conf['new']['order'] == 0: if conf['new']['order'] == 0:
self.randomizeCards(did) self.randomizeCards(did)
@ -1431,7 +1427,7 @@ and due >= ? and queue = 0""" % scids, now, self.col.usn(), shiftby, low)
self.orderCards(did) self.orderCards(did)
# for post-import # for post-import
def maybeRandomizeDeck(self, did=None) -> None: def maybeRandomizeDeck(self, did=None):
if not did: if not did:
did = self.col.decks.selected() did = self.col.decks.selected()
conf = self.col.decks.confForDid(did) conf = self.col.decks.confForDid(did)