Merge branch 'cleanup'
This commit is contained in:
commit
abd2fea76f
@ -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()
|
||||||
|
@ -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):
|
||||||
|
@ -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 = []
|
||||||
|
|
Loading…
Reference in New Issue
Block a user