Merge branch 'cleanup'

This commit is contained in:
Alex Yatskov 2021-01-18 12:37:03 -08:00
commit abd2fea76f
3 changed files with 197 additions and 184 deletions

View File

@ -1,4 +1,4 @@
# Copyright 2016-2020 Alex Yatskov # Copyright 2016-2021 Alex Yatskov
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -32,6 +32,7 @@ import anki
import anki.exporting import anki.exporting
import anki.storage import anki.storage
import aqt import aqt
from anki.exporting import AnkiPackageExporter from anki.exporting import AnkiPackageExporter
from anki.importing import AnkiPackageImporter from anki.importing import AnkiPackageImporter
from anki.utils import joinFields, intTime, guid64, fieldChecksum from anki.utils import joinFields, intTime, guid64, fieldChecksum
@ -132,48 +133,48 @@ class AnkiConnect:
reviewer = self.window().reviewer reviewer = self.window().reviewer
if reviewer is None: if reviewer is None:
raise Exception('reviewer is not available') raise Exception('reviewer is not available')
else:
return reviewer return reviewer
def collection(self): def collection(self):
collection = self.window().col collection = self.window().col
if collection is None: if collection is None:
raise Exception('collection is not available') raise Exception('collection is not available')
else:
return collection return collection
def decks(self): def decks(self):
decks = self.collection().decks decks = self.collection().decks
if decks is None: if decks is None:
raise Exception('decks are not available') raise Exception('decks are not available')
else:
return decks return decks
def scheduler(self): def scheduler(self):
scheduler = self.collection().sched scheduler = self.collection().sched
if scheduler is None: if scheduler is None:
raise Exception('scheduler is not available') raise Exception('scheduler is not available')
else:
return scheduler return scheduler
def database(self): def database(self):
database = self.collection().db database = self.collection().db
if database is None: if database is None:
raise Exception('database is not available') raise Exception('database is not available')
else:
return database return database
def media(self): def media(self):
media = self.collection().media media = self.collection().media
if media is None: if media is None:
raise Exception('media is not available') raise Exception('media is not available')
else:
return media return media
def startEditing(self): def startEditing(self):
@ -209,49 +210,54 @@ class AnkiConnect:
duplicateScope = None duplicateScope = None
duplicateScopeDeckName = None duplicateScopeDeckName = None
duplicateScopeCheckChildren = False duplicateScopeCheckChildren = False
if 'options' in note:
if 'allowDuplicate' in note['options']:
allowDuplicate = note['options']['allowDuplicate']
if type(allowDuplicate) is not bool:
raise Exception('option parameter \'allowDuplicate\' must be boolean')
if 'duplicateScope' in note['options']:
duplicateScope = note['options']['duplicateScope']
if 'duplicateScopeOptions' in note['options']:
duplicateScopeOptions = note['options']['duplicateScopeOptions']
if 'deckName' in duplicateScopeOptions:
duplicateScopeDeckName = duplicateScopeOptions['deckName']
if 'checkChildren' in duplicateScopeOptions:
duplicateScopeCheckChildren = duplicateScopeOptions['checkChildren']
if type(duplicateScopeCheckChildren) is not bool:
raise Exception('option parameter \'duplicateScopeOptions.checkChildren\' must be boolean')
duplicateOrEmpty = self.isNoteDuplicateOrEmptyInScope(ankiNote, deck, collection, duplicateScope, duplicateScopeDeckName, duplicateScopeCheckChildren) if 'options' in note:
if 'allowDuplicate' in note['options']:
allowDuplicate = note['options']['allowDuplicate']
if type(allowDuplicate) is not bool:
raise Exception('option parameter "allowDuplicate" must be boolean')
if 'duplicateScope' in note['options']:
duplicateScope = note['options']['duplicateScope']
if 'duplicateScopeOptions' in note['options']:
duplicateScopeOptions = note['options']['duplicateScopeOptions']
if 'deckName' in duplicateScopeOptions:
duplicateScopeDeckName = duplicateScopeOptions['deckName']
if 'checkChildren' in duplicateScopeOptions:
duplicateScopeCheckChildren = duplicateScopeOptions['checkChildren']
if type(duplicateScopeCheckChildren) is not bool:
raise Exception('option parameter "duplicateScopeOptions.checkChildren" must be boolean')
duplicateOrEmpty = self.isNoteDuplicateOrEmptyInScope(
ankiNote,
deck,
collection,
duplicateScope,
duplicateScopeDeckName,
duplicateScopeCheckChildren
)
if duplicateOrEmpty == 1: if duplicateOrEmpty == 1:
raise Exception('cannot create note because it is empty') raise Exception('cannot create note because it is empty')
elif duplicateOrEmpty == 2: elif duplicateOrEmpty == 2:
if not allowDuplicate: if allowDuplicate:
return ankiNote
raise Exception('cannot create note because it is a duplicate') raise Exception('cannot create note because it is a duplicate')
else:
return ankiNote
elif duplicateOrEmpty == 0: elif duplicateOrEmpty == 0:
return ankiNote return ankiNote
else: else:
raise Exception('cannot create note for unknown reason') raise Exception('cannot create note for unknown reason')
def isNoteDuplicateOrEmptyInScope(self, note, deck, collection, duplicateScope, duplicateScopeDeckName, duplicateScopeCheckChildren): def isNoteDuplicateOrEmptyInScope(self, note, deck, collection, duplicateScope, duplicateScopeDeckName, duplicateScopeCheckChildren):
"1 if first is empty; 2 if first is a duplicate, 0 otherwise." # 1 if first is empty; 2 if first is a duplicate, 0 otherwise.
if duplicateScope != 'deck': if duplicateScope != 'deck':
result = note.dupeOrEmpty() return note.dupeOrEmpty() or 0
if result == False:
return 0
return result
# dupeOrEmpty returns if a note is a global duplicate # dupeOrEmpty returns if a note is a global duplicate
# the rest of the function checks to see if the note is a duplicate in the deck # the rest of the function checks to see if the note is a duplicate in the deck
val = note.fields[0] val = note.fields[0]
if not val.strip(): if not val.strip():
return 1 return 1
csum = anki.utils.fieldChecksum(val)
did = deck['id'] did = deck['id']
if duplicateScopeDeckName is not None: if duplicateScopeDeckName is not None:
@ -261,24 +267,17 @@ class AnkiConnect:
return 0 return 0
did = deck2['id'] did = deck2['id']
dids = {} dids = {did: True}
dids[did] = True
if duplicateScopeCheckChildren: if duplicateScopeCheckChildren:
for kv in collection.decks.children(did): for kv in collection.decks.children(did):
dids[kv[1]] = True dids[kv[1]] = True
for noteId in note.col.db.list( csum = anki.utils.fieldChecksum(val)
"select id from notes where csum = ? and id != ? and mid = ?", for noteId in note.col.db.list('select id from notes where csum = ? and id != ? and mid = ?', csum, note.id or 0, note.mid):
csum, for cardDeckId in note.col.db.list('select did from cards where nid = ?', noteId):
note.id or 0,
note.mid,
):
for cardDeckId in note.col.db.list(
"select did from cards where nid = ?",
noteId
):
if cardDeckId in dids: if cardDeckId in dids:
return 2 return 2
return 0 return 0
@ -290,23 +289,27 @@ class AnkiConnect:
def version(self): def version(self):
return util.setting('apiVersion') return util.setting('apiVersion')
@util.api() @util.api()
def getProfiles(self): def getProfiles(self):
return self.window().pm.profiles() return self.window().pm.profiles()
@util.api() @util.api()
def loadProfile(self, name): def loadProfile(self, name):
if name not in self.window().pm.profiles(): if name not in self.window().pm.profiles():
return False return False
if not self.window().isVisible():
self.window().pm.load(name) if self.window().isVisible():
self.window().loadProfile()
self.window().profileDiag.closeWithoutQuitting()
else:
cur_profile = self.window().pm.name cur_profile = self.window().pm.name
if cur_profile != name: if cur_profile != name:
self.window().unloadProfileAndShowProfileManager() self.window().unloadProfileAndShowProfileManager()
self.loadProfile(name) self.loadProfile(name)
else:
self.window().pm.load(name)
self.window().loadProfile()
self.window().profileDiag.closeWithoutQuitting()
return True return True
@ -407,7 +410,7 @@ class AnkiConnect:
@util.api() @util.api()
def getDeckConfig(self, deck): def getDeckConfig(self, deck):
if not deck in self.deckNames(): if deck not in self.deckNames():
return False return False
collection = self.collection() collection = self.collection()
@ -423,7 +426,7 @@ class AnkiConnect:
config['mod'] = anki.utils.intTime() config['mod'] = anki.utils.intTime()
config['usn'] = collection.usn() config['usn'] = collection.usn()
if not config['id'] in collection.decks.dconf: if config['id'] not in collection.decks.dconf:
return False return False
collection.decks.dconf[config['id']] = config collection.decks.dconf[config['id']] = config
@ -439,7 +442,7 @@ class AnkiConnect:
return False return False
collection = self.collection() collection = self.collection()
if not configId in collection.decks.dconf: if configId not in collection.decks.dconf:
return False return False
for deck in decks: for deck in decks:
@ -452,7 +455,7 @@ class AnkiConnect:
@util.api() @util.api()
def cloneDeckConfigId(self, name, cloneFrom='1'): def cloneDeckConfigId(self, name, cloneFrom='1'):
configId = str(cloneFrom) configId = str(cloneFrom)
if not configId in self.collection().decks.dconf: if configId not in self.collection().decks.dconf:
return False return False
config = self.collection().decks.getConf(configId) config = self.collection().decks.getConf(configId)
@ -463,7 +466,7 @@ class AnkiConnect:
def removeDeckConfigId(self, configId): def removeDeckConfigId(self, configId):
configId = str(configId) configId = str(configId)
collection = self.collection() collection = self.collection()
if configId == 1 or not configId in collection.decks.dconf: if configId not in collection.decks.dconf:
return False return False
collection.decks.remConf(configId) collection.decks.remConf(configId)
@ -599,6 +602,7 @@ class AnkiConnect:
ankiNote.flush() ankiNote.flush()
@util.api() @util.api()
def addTags(self, notes, tags, add=True): def addTags(self, notes, tags, add=True):
self.startEditing() self.startEditing()
@ -615,61 +619,60 @@ class AnkiConnect:
def getTags(self): def getTags(self):
return self.collection().tags.all() return self.collection().tags.all()
@util.api() @util.api()
def clearUnusedTags(self): def clearUnusedTags(self):
self.collection().tags.registerNotes() self.collection().tags.registerNotes()
@util.api() @util.api()
def replaceTags(self, notes, tag_to_replace, replace_with_tag): def replaceTags(self, notes, tag_to_replace, replace_with_tag):
if self.collection() is not None: self.window().progress.start()
self.window().progress.start()
for nid in notes: for nid in notes:
note = self.collection().getNote(nid) note = self.collection().getNote(nid)
if note.hasTag(tag_to_replace): if note.hasTag(tag_to_replace):
note.delTag(tag_to_replace) note.delTag(tag_to_replace)
note.addtag(replace_with_tag) note.addtag(replace_with_tag)
note.flush() note.flush()
self.window().requireReset()
self.window().progress.finish() self.window().requireReset()
self.window().reset() self.window().progress.finish()
self.window().reset()
@util.api() @util.api()
def replaceTagsInAllNotes(self, tag_to_replace, replace_with_tag): def replaceTagsInAllNotes(self, tag_to_replace, replace_with_tag):
collection = self.collection() self.window().progress.start()
if collection is not None:
nids = collection.db.list('select id from notes') for nid in collection.db.list('select id from notes'):
self.window().progress.start() note = collection.getNote(nid)
for nid in nids: if note.hasTag(tag_to_replace):
note = collection.getNote(nid) note.delTag(tag_to_replace)
if note.hasTag(tag_to_replace): note.addtag(replace_with_tag)
note.delTag(tag_to_replace) note.flush()
note.addtag(replace_with_tag)
note.flush() self.window().requireReset()
self.window().requireReset() self.window().progress.finish()
self.window().progress.finish() self.window().reset()
self.window().reset()
return False
@util.api() @util.api()
def setEaseFactors(self, cards, easeFactors): def setEaseFactors(self, cards, easeFactors):
couldSetEaseFactors = [] couldSetEaseFactors = []
ind = 0 for i, card in enumerate(cards):
for card in cards:
ankiCard = self.collection().getCard(card) ankiCard = self.collection().getCard(card)
if ankiCard is None: if ankiCard is None:
raise Exception('card was not found: {}'.format(card['id']))
couldSetEaseFactors.append(False) couldSetEaseFactors.append(False)
else: else:
couldSetEaseFactors.append(True) couldSetEaseFactors.append(True)
ankiCard.factor = easeFactors[ind] ankiCard.factor = easeFactors[ind]
ankiCard.flush() ankiCard.flush()
ind += 1
return couldSetEaseFactors return couldSetEaseFactors
@util.api() @util.api()
def getEaseFactors(self, cards): def getEaseFactors(self, cards):
easeFactors = [] easeFactors = []
@ -679,6 +682,7 @@ class AnkiConnect:
return easeFactors return easeFactors
@util.api() @util.api()
def suspend(self, cards, suspend=True): def suspend(self, cards, suspend=True):
for card in cards: for card in cards:
@ -759,11 +763,11 @@ class AnkiConnect:
@util.api() @util.api()
def createModel(self, modelName, inOrderFields, cardTemplates, css = None): def createModel(self, modelName, inOrderFields, cardTemplates, css = None):
# https://github.com/dae/anki/blob/b06b70f7214fb1f2ce33ba06d2b095384b81f874/anki/stdmodels.py # https://github.com/dae/anki/blob/b06b70f7214fb1f2ce33ba06d2b095384b81f874/anki/stdmodels.py
if (len(inOrderFields) == 0): if len(inOrderFields) == 0:
raise Exception('Must provide at least one field for inOrderFields') raise Exception('Must provide at least one field for inOrderFields')
if (len(cardTemplates) == 0): if len(cardTemplates) == 0:
raise Exception('Must provide at least one card for cardTemplates') raise Exception('Must provide at least one card for cardTemplates')
if (modelName in self.collection().models.allNames()): if modelName in self.collection().models.allNames():
raise Exception('Model name already exists') raise Exception('Model name already exists')
collection = self.collection() collection = self.collection()
@ -855,6 +859,7 @@ class AnkiConnect:
return templates return templates
@util.api() @util.api()
def modelTemplates(self, modelName): def modelTemplates(self, modelName):
model = self.collection().models.byName(modelName) model = self.collection().models.byName(modelName)
@ -885,7 +890,6 @@ class AnkiConnect:
raise Exception('model was not found: {}'.format(model['name'])) raise Exception('model was not found: {}'.format(model['name']))
templates = model['templates'] templates = model['templates']
for ankiTemplate in ankiModel['tmpls']: for ankiTemplate in ankiModel['tmpls']:
template = templates.get(ankiTemplate['name']) template = templates.get(ankiTemplate['name'])
if template: if template:
@ -919,24 +923,24 @@ class AnkiConnect:
deck = self.collection().decks.get(deckId) deck = self.collection().decks.get(deckId)
if deck is None: if deck is None:
raise Exception('deck was not found: {}'.format(deckId)) raise Exception('deck was not found: {}'.format(deckId))
else:
return deck['name'] return deck['name']
@util.api() @util.api()
def findNotes(self, query=None): def findNotes(self, query=None):
if query is None: if query is None:
return [] return []
else:
return list(map(int, self.collection().findNotes(query))) return list(map(int, self.collection().findNotes(query)))
@util.api() @util.api()
def findCards(self, query=None): def findCards(self, query=None):
if query is None: if query is None:
return [] return []
else:
return list(map(int, self.collection().findCards(query))) return list(map(int, self.collection().findCards(query)))
@util.api() @util.api()
@ -957,8 +961,8 @@ class AnkiConnect:
'cardId': card.id, 'cardId': card.id,
'fields': fields, 'fields': fields,
'fieldOrder': card.ord, 'fieldOrder': card.ord,
'question': util.getQuestion(card), 'question': util.cardQuestion(card),
'answer': util.getAnswer(card), 'answer': util.cardAnswer(card),
'modelName': model['name'], 'modelName': model['name'],
'ord': card.ord, 'ord': card.ord,
'deckName': self.deckNameFromId(card.did), 'deckName': self.deckNameFromId(card.did),
@ -987,65 +991,74 @@ class AnkiConnect:
@util.api() @util.api()
def cardReviews(self, deck, startID): def cardReviews(self, deck, startID):
return self.database().all("select id, cid, usn, ease, ivl, lastIvl, factor, time, type from revlog " return self.database().all(
"where id>? and cid in (select id from cards where did=?)", 'select id, cid, usn, ease, ivl, lastIvl, factor, time, type from revlog ''where id>? and cid in (select id from cards where did=?)',
startID, self.decks().id(deck)) startID,
self.decks().id(deck)
)
@util.api() @util.api()
def reloadCollection(self): def reloadCollection(self):
self.collection().reset() self.collection().reset()
@util.api() @util.api()
def getLatestReviewID(self, deck): def getLatestReviewID(self, deck):
return self.database().scalar("select max(id) from revlog where cid in (select id from cards where did=?)", return self.database().scalar(
self.decks().id(deck)) or 0 'select max(id) from revlog where cid in (select id from cards where did=?)',
self.decks().id(deck)
) or 0
@util.api() @util.api()
def updateCompleteDeck(self, data): def updateCompleteDeck(self, data):
self.startEditing() self.startEditing()
did = self.decks().id(data["deck"]) did = self.decks().id(data['deck'])
self.decks().flush() self.decks().flush()
model_manager = self.collection().models model_manager = self.collection().models
for _, card in data["cards"].items(): for _, card in data['cards'].items():
self.database().execute( self.database().execute(
"replace into cards (id, nid, did, ord, type, queue, due, ivl, factor, reps, lapses, left, " 'replace into cards (id, nid, did, ord, type, queue, due, ivl, factor, reps, lapses, left, '
"mod, usn, odue, odid, flags, data) " 'mod, usn, odue, odid, flags, data) '
"values (" + "?," * (12 + 6 - 1) + "?)", 'values (' + '?,' * (12 + 6 - 1) + '?)',
card["id"], card["nid"], did, card["ord"], card["type"], card["queue"], card["due"], card['id'], card['nid'], did, card['ord'], card['type'], card['queue'], card['due'],
card["ivl"], card["factor"], card["reps"], card["lapses"], card["left"], card['ivl'], card['factor'], card['reps'], card['lapses'], card['left'],
intTime(), -1, 0, 0, 0, 0 intTime(), -1, 0, 0, 0, 0
) )
note = data["notes"][str(card["nid"])] note = data['notes'][str(card['nid'])]
tags = self.collection().tags.join(self.collection().tags.canonify(note["tags"])) tags = self.collection().tags.join(self.collection().tags.canonify(note['tags']))
self.database().execute( self.database().execute(
"replace into notes(id, mid, tags, flds," 'replace into notes(id, mid, tags, flds,'
"guid, mod, usn, flags, data, sfld, csum) values (" + "?," * (4 + 7 - 1) + "?)", 'guid, mod, usn, flags, data, sfld, csum) values (' + '?,' * (4 + 7 - 1) + '?)',
note["id"], note["mid"], tags, joinFields(note["fields"]), note['id'], note['mid'], tags, joinFields(note['fields']),
guid64(), intTime(), -1, 0, 0, "", fieldChecksum(note["fields"][0]) guid64(), intTime(), -1, 0, 0, '', fieldChecksum(note['fields'][0])
) )
model = data["models"][str(note["mid"])] model = data['models'][str(note['mid'])]
if not model_manager.get(model["id"]): if not model_manager.get(model['id']):
model_o = model_manager.new(model["name"]) model_o = model_manager.new(model['name'])
for field_name in model["fields"]: for field_name in model['fields']:
field = model_manager.newField(field_name) field = model_manager.newField(field_name)
model_manager.addField(model_o, field) model_manager.addField(model_o, field)
for template_name in model["templateNames"]: for template_name in model['templateNames']:
template = model_manager.newTemplate(template_name) template = model_manager.newTemplate(template_name)
model_manager.addTemplate(model_o, template) model_manager.addTemplate(model_o, template)
model_o["id"] = model["id"] model_o['id'] = model['id']
model_manager.update(model_o) model_manager.update(model_o)
model_manager.flush() model_manager.flush()
self.stopEditing() self.stopEditing()
@util.api() @util.api()
def insertReviews(self, reviews): def insertReviews(self, reviews):
if len(reviews) == 0: return if len(reviews) > 0:
sql = "insert into revlog(id,cid,usn,ease,ivl,lastIvl,factor,time,type) values " sql = 'insert into revlog(id,cid,usn,ease,ivl,lastIvl,factor,time,type) values '
for row in reviews: for row in reviews:
sql += "(%s)," % ",".join(map(str, row)) sql += '(%s),' % ','.join(map(str, row))
sql = sql[:-1] sql = sql[:-1]
self.database().execute(sql) self.database().execute(sql)
@util.api() @util.api()
def notesInfo(self, notes): def notesInfo(self, notes):
@ -1085,6 +1098,7 @@ class AnkiConnect:
finally: finally:
self.stopEditing() self.stopEditing()
@util.api() @util.api()
def removeEmptyNotes(self): def removeEmptyNotes(self):
for model in self.collection().models.all(): for model in self.collection().models.all():
@ -1115,7 +1129,6 @@ class AnkiConnect:
@util.api() @util.api()
def guiAddCards(self, note=None): def guiAddCards(self, note=None):
if note is not None: if note is not None:
collection = self.collection() collection = self.collection()
@ -1123,15 +1136,15 @@ class AnkiConnect:
if deck is None: if deck is None:
raise Exception('deck was not found: {}'.format(note['deckName'])) raise Exception('deck was not found: {}'.format(note['deckName']))
self.collection().decks.select(deck['id']) collection.decks.select(deck['id'])
savedMid = deck.pop('mid', None) savedMid = deck.pop('mid', None)
model = collection.models.byName(note['modelName']) model = collection.models.byName(note['modelName'])
if model is None: if model is None:
raise Exception('model was not found: {}'.format(note['modelName'])) raise Exception('model was not found: {}'.format(note['modelName']))
self.collection().models.setCurrent(model) collection.models.setCurrent(model)
self.collection().models.update(model) collection.models.update(model)
closeAfterAdding = False closeAfterAdding = False
if note is not None and 'options' in note: if note is not None and 'options' in note:
@ -1156,8 +1169,8 @@ class AnkiConnect:
self.modelHasChanged = True self.modelHasChanged = True
super().__init__(mw) super().__init__(mw)
self.addButton.setText("Add and Close") self.addButton.setText('Add and Close')
self.addButton.setShortcut(aqt.qt.QKeySequence("Ctrl+Return")) self.addButton.setShortcut(aqt.qt.QKeySequence('Ctrl+Return'))
def _addCards(self): def _addCards(self):
super()._addCards() super()._addCards()
@ -1259,6 +1272,7 @@ class AnkiConnect:
return addCards.editor.note.id return addCards.editor.note.id
@util.api() @util.api()
def guiReviewActive(self): def guiReviewActive(self):
return self.reviewer().card is not None and self.window().state == 'review' return self.reviewer().card is not None and self.window().state == 'review'
@ -1279,21 +1293,21 @@ class AnkiConnect:
order = info['ord'] order = info['ord']
name = info['name'] name = info['name']
fields[name] = {'value': note.fields[order], 'order': order} fields[name] = {'value': note.fields[order], 'order': order}
if card is not None:
buttonList = reviewer._answerButtonList() buttonList = reviewer._answerButtonList()
return { return {
'cardId': card.id, 'cardId': card.id,
'fields': fields, 'fields': fields,
'fieldOrder': card.ord, 'fieldOrder': card.ord,
'question': util.getQuestion(card), 'question': util.cardQuestion(card),
'answer': util.getAnswer(card), 'answer': util.cardAnswer(card),
'buttons': [b[0] for b in buttonList], 'buttons': [b[0] for b in buttonList],
'nextReviews': [reviewer.mw.col.sched.nextIvlStr(reviewer.card, b[0], True) for b in buttonList], 'nextReviews': [reviewer.mw.col.sched.nextIvlStr(reviewer.card, b[0], True) for b in buttonList],
'modelName': model['name'], 'modelName': model['name'],
'deckName': self.deckNameFromId(card.did), 'deckName': self.deckNameFromId(card.did),
'css': model['css'], 'css': model['css'],
'template': card.template()['name'] 'template': card.template()['name']
} }
@util.api() @util.api()
@ -1302,12 +1316,11 @@ class AnkiConnect:
return False return False
card = self.reviewer().card card = self.reviewer().card
if card is not None: if card is not None:
card.startTimer() card.startTimer()
return True return True
else:
return False return False
@util.api() @util.api()
@ -1315,8 +1328,8 @@ class AnkiConnect:
if self.guiReviewActive(): if self.guiReviewActive():
self.reviewer()._showQuestion() self.reviewer()._showQuestion()
return True return True
else:
return False return False
@util.api() @util.api()
@ -1324,8 +1337,8 @@ class AnkiConnect:
if self.guiReviewActive(): if self.guiReviewActive():
self.window().reviewer._showAnswer() self.window().reviewer._showAnswer()
return True return True
else:
return False return False
@util.api() @util.api()
@ -1366,8 +1379,8 @@ class AnkiConnect:
if self.guiDeckOverview(name): if self.guiDeckOverview(name):
self.window().moveToState('review') self.window().moveToState('review')
return True return True
else:
return False return False
@util.api() @util.api()
@ -1383,7 +1396,7 @@ class AnkiConnect:
for note in notes: for note in notes:
try: try:
results.append(self.addNote(note)) results.append(self.addNote(note))
except Exception: except:
results.append(None) results.append(None)
return results return results
@ -1409,8 +1422,10 @@ class AnkiConnect:
exporter.includeSched = includeSched exporter.includeSched = includeSched
exporter.exportInto(path) exporter.exportInto(path)
return True return True
return False return False
@util.api() @util.api()
def importPackage(self, path): def importPackage(self, path):
collection = self.collection() collection = self.collection()

View File

@ -1,4 +1,4 @@
# Copyright 2016-2020 Alex Yatskov # Copyright 2016-2021 Alex Yatskov
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -24,11 +24,12 @@ import enum
# Utilities # Utilities
# #
class MediaType (enum.Enum): class MediaType(enum.Enum):
Audio = 1 Audio = 1
Video = 2 Video = 2
Picture = 3 Picture = 3
def download(url): def download(url):
client = anki.sync.AnkiRequestsClient() client = anki.sync.AnkiRequestsClient()
client.timeout = setting('webTimeout') / 1000 client.timeout = setting('webTimeout') / 1000
@ -49,20 +50,18 @@ def api(*versions):
return decorator return decorator
def getQuestion(card): def cardQuestion(card):
if getattr(card, 'question', None) is None: if getattr(card, 'question', None) is None:
question = card._getQA()['q'] return card._getQA()['q']
else:
question = card.question(), return card.question(),
return question
def getAnswer(card): def cardAnswer(card):
if getattr(card, 'answer', None) is None: if getattr(card, 'answer', None) is None:
answer = card._getQA()['a'] return card._getQA()['a']
else:
answer = card.answer() return card.answer()
return answer
def setting(key): def setting(key):

View File

@ -1,4 +1,4 @@
# Copyright 2016-2020 Alex Yatskov # Copyright 2016-2021 Alex Yatskov
# #
# This program is free software: you can redistribute it and/or modify # This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by # it under the terms of the GNU General Public License as published by
@ -17,7 +17,7 @@ import json
import select import select
import socket import socket
from . import web, util from . import util
# #
# WebRequest # WebRequest
@ -210,4 +210,3 @@ class WebServer:
client.close() client.close()
self.clients = [] self.clients = []