work in progress on cleaning up api

This commit is contained in:
Alex Yatskov 2018-05-06 17:52:24 -07:00
parent 4173f4e5fe
commit e80bf2e8aa

View File

@ -47,7 +47,7 @@ URL_UPGRADE = 'https://raw.githubusercontent.com/FooSoft/anki-connect/master/A
# #
# General helpers # Helpers
# #
if sys.version_info[0] < 3: if sys.version_info[0] < 3:
@ -66,19 +66,6 @@ else:
from PyQt5.QtWidgets import QMessageBox from PyQt5.QtWidgets import QMessageBox
#
# Helpers
#
def webApi(*versions):
def decorator(func):
method = lambda *args, **kwargs: func(*args, **kwargs)
setattr(method, 'versions', versions)
setattr(method, 'api', True)
return method
return decorator
def makeBytes(data): def makeBytes(data):
return data.encode('utf-8') return data.encode('utf-8')
@ -87,35 +74,14 @@ def makeStr(data):
return data.decode('utf-8') return data.decode('utf-8')
def download(url): def api(*versions):
try: def decorator(func):
resp = web.urlopen(url, timeout=URL_TIMEOUT) method = lambda *args, **kwargs: func(*args, **kwargs)
except web.URLError as e: setattr(method, 'versions', versions)
raise Exception('A urlError has occurred for url ' + url + '. Error messages was: ' + e.message) setattr(method, 'api', True)
return method
if resp.code != 200: return decorator
raise Exception('Return code for url request' + url + 'was not 200. Error code: ' + resp.code)
return resp.read()
def audioInject(note, fields, filename):
for field in fields:
if field in note:
note[field] += u'[sound:{}]'.format(filename)
def verifyString(string):
t = type(string)
return t == str or t == unicode
def verifyStringList(strings):
for s in strings:
if not verifyString(s):
return False
return True
# #
@ -212,7 +178,7 @@ class WebServer:
def setHeader(self, name, value): def setHeader(self, name, value):
self.extraHeaders[name] = value self.headersOpt[name] = value
def resetHeaders(self): def resetHeaders(self):
@ -221,13 +187,14 @@ class WebServer:
['Content-Type', 'text/json'], ['Content-Type', 'text/json'],
['Access-Control-Allow-Origin', '*'] ['Access-Control-Allow-Origin', '*']
] ]
self.extraHeaders = {} self.headersOpt = {}
def getHeaders(self): def getHeaders(self):
headers = self.headers[:] headers = self.headers[:]
for name in self.extraHeaders: for name in self.headersOpt:
headers.append([name, self.extraHeaders[name]]) headers.append([name, self.headersOpt[name]])
return headers return headers
@ -301,49 +268,7 @@ class WebServer:
# #
# AnkiNoteParams # AnkiConnect
#
class AnkiNoteParams:
def __init__(self, params):
self.deckName = params.get('deckName')
self.modelName = params.get('modelName')
self.fields = params.get('fields', {})
self.tags = params.get('tags', [])
class Audio:
def __init__(self, params):
self.url = params.get('url')
self.filename = params.get('filename')
self.skipHash = params.get('skipHash')
self.fields = params.get('fields', [])
def validate(self):
return (
verifyString(self.url) and
verifyString(self.filename) and os.path.dirname(self.filename) == '' and
verifyStringList(self.fields) and
(verifyString(self.skipHash) or self.skipHash is None)
)
audio = Audio(params.get('audio', {}))
self.audio = audio if audio.validate() else None
def validate(self):
return (
verifyString(self.deckName) and
verifyString(self.modelName) and
type(self.fields) == dict and verifyStringList(list(self.fields.keys())) and verifyStringList(list(self.fields.values())) and
type(self.tags) == list and verifyStringList(self.tags)
)
def __str__(self):
return 'DeckName: ' + self.deckName + '. ModelName: ' + self.modelName + '. Fields: ' + str(self.fields) + '. Tags: ' + str(self.tags) + '.'
#
# AnkiBridge
# #
class AnkiConnect: class AnkiConnect:
@ -407,6 +332,50 @@ class AnkiConnect:
return reply['result'] return reply['result']
def download(self, url):
resp = web.urlopen(url, timeout=URL_TIMEOUT)
if resp.code == 200:
return resp.read()
else:
raise Exception('return code for download of {} was {}'.format(url, resp.code))
def window(self):
return aqt.mw
def reviewer(self):
reviewer = self.window().reviewer
if reviewer is None:
raise Exception('reviewer is not available')
else:
return reviewer
def collection(self):
collection = self.window().col
if collection is None:
raise Exception('collection is not available')
else:
return collection
def scheduler(self):
scheduler = self.collection().sched
if scheduler is None:
raise Exception('scheduler is not available')
else:
return scheduler
def media(self):
media = self.collection().media
if media is None:
raise Exception('media is not available')
else:
return media
def startEditing(self): def startEditing(self):
self.window().requireReset() self.window().requireReset()
@ -416,68 +385,44 @@ class AnkiConnect:
self.window().maybeReset() self.window().maybeReset()
def window(self): def createNote(self, note):
return aqt.mw
def reviewer(self):
return self.window().reviewer
def collection(self):
return self.window().col
def scheduler(self):
return self.collection().sched
def media(self):
collection = self.collection() collection = self.collection()
if collection is not None:
return collection.media
model = collection.models.byName(note['modelName'])
def createNote(self, params):
collection = self.collection()
if collection is None:
raise Exception('Collection was not found.')
model = collection.models.byName(params.modelName)
if model is None: if model is None:
raise Exception('Model was not found for model: ' + params.modelName) raise Exception('model was not found: {}'.format(note['modelName']))
deck = collection.decks.byName(params.deckName) deck = collection.decks.byName(note['deckName'])
if deck is None: if deck is None:
raise Exception('Deck was not found for deck: ' + params.deckName) raise Exception('deck was not found: {}'.format(note['deckName']))
note = anki.notes.Note(collection, model) ankiNote = anki.notes.Note(collection, model)
note.model()['did'] = deck['id'] ankiNote.model()['did'] = deck['id']
note.tags = params.tags ankiNote.tags = note['tags']
for name, value in params.fields.items(): for name, value in note['fields'].items():
if name in note: if name in ankiNote:
note[name] = value ankiNote[name] = value
# Returns 1 if empty. 2 if duplicate. Otherwise returns False duplicateOrEmpty = ankiNote.dupeOrEmpty()
duplicateOrEmpty = note.dupeOrEmpty()
if duplicateOrEmpty == 1: if duplicateOrEmpty == 1:
raise Exception('Note was empty. Param were: ' + str(params)) raise Exception('cannot create note because it is empty')
elif duplicateOrEmpty == 2: elif duplicateOrEmpty == 2:
raise Exception('Note is duplicate of existing note. Params were: ' + str(params)) raise Exception('cannot create note because it is a duplicte')
elif duplicateOrEmpty == False: elif duplicateOrEmpty == False:
return note return ankiNote
else:
raise Exception('cannot create note for unknown reason')
@webApi() @api()
def storeMediaFile(self, filename, data): def storeMediaFile(self, filename, data):
self.deleteMediaFile(filename) self.deleteMediaFile(filename)
self.media().writeData(filename, base64.b64decode(data)) self.media().writeData(filename, base64.b64decode(data))
@webApi() @api()
def retrieveMediaFile(self, filename): def retrieveMediaFile(self, filename):
# based on writeData from anki/media.py
filename = os.path.basename(filename) filename = os.path.basename(filename)
filename = normalize('NFC', filename) filename = normalize('NFC', filename)
filename = self.media().stripIllegal(filename) filename = self.media().stripIllegal(filename)
@ -490,224 +435,201 @@ class AnkiConnect:
return False return False
@webApi() @api()
def deleteMediaFile(self, filename): def deleteMediaFile(self, filename):
self.media().syncDelete(filename) self.media().syncDelete(filename)
@webApi() @api()
def addNote(self, note): def addNote(self, note):
params = AnkiNoteParams(note) ankiNote = self.createNote(note)
if not params.validate():
raise Exception('Invalid note parameters')
collection = self.collection() if note['audio'] is not None and len(note['audio']['fields']) > 0:
if collection is None: audio = note['audio']
raise Exception('Collection was not found.') data = download(audio['url'])
note = self.createNote(params)
if note is None:
raise Exception('Failed to create note from params: ' + str(params))
if params.audio is not None and len(params.audio.fields) > 0:
data = download(params.audio.url)
if data is not None: if data is not None:
if params.audio.skipHash is None: if audio['skipHash'] is None:
skip = False skip = False
else: else:
m = hashlib.md5() m = hashlib.md5()
m.update(data) m.update(data)
skip = params.audio.skipHash == m.hexdigest() skip = audio['skipHash'] == m.hexdigest()
if not skip: if not skip:
audioInject(note, params.audio.fields, params.audio.filename) for field in audio['fields']:
self.media().writeData(params.audio.filename, data) if field in ankiNote:
ankiNote[field] += u'[sound:{}]'.format(audio['filename'])
self.media().writeData(audio['filename'], data)
collection = self.collection()
self.startEditing() self.startEditing()
collection.addNote(note) collection.addNote(ankiNote)
collection.autosave() collection.autosave()
self.stopEditing() self.stopEditing()
return note.id return ankiNote.id
@webApi() @api()
def canAddNote(self, note): def canAddNote(self, note):
params = AnkiNoteParams(note)
if not params.validate():
return False
try: try:
return bool(self.createNote(params)) return bool(self.createNote(note))
except: except:
return False return False
@webApi() @api()
def updateNoteFields(self, params): def updateNoteFields(self, params):
collection = self.collection() note = self.collection().getNote(params['id'])
if collection is None:
raise Exception('Collection was not found.')
note = collection.getNote(params['id'])
if note is None: if note is None:
raise Exception('Failed to get note:{}'.format(params['id'])) raise Exception('note was not found: {}'.format(params['id']))
for name, value in params['fields'].items(): for name, value in params['fields'].items():
if name in note: if name in note:
note[name] = value note[name] = value
note.flush() note.flush()
@webApi() @api()
def addTags(self, notes, tags, add=True): def addTags(self, notes, tags, add=True):
self.startEditing() self.startEditing()
self.collection().tags.bulkAdd(notes, tags, add) self.collection().tags.bulkAdd(notes, tags, add)
self.stopEditing() self.stopEditing()
@webApi() @api()
def removeTags(self, notes, tags): def removeTags(self, notes, tags):
return self.addTags(notes, tags, False) return self.addTags(notes, tags, False)
@webApi() @api()
def getTags(self): def getTags(self):
return self.collection().tags.all() return self.collection().tags.all()
@webApi() @api()
def suspend(self, cards, suspend=True): def suspend(self, cards, suspend=True):
for card in cards: for card in cards:
isSuspended = self.isSuspended(card) if self.suspended(card) == suspend:
if suspend and isSuspended:
cards.remove(card)
elif not suspend and not isSuspended:
cards.remove(card) cards.remove(card)
if cards: if len(cards) == 0:
self.startEditing()
if suspend:
self.collection().sched.suspendCards(cards)
else:
self.collection().sched.unsuspendCards(cards)
self.stopEditing()
return True
return False return False
scheduler = self.scheduler()
self.startEditing()
if suspend:
scheduler.suspendCards(cards)
else:
scheduler.unsuspendCards(cards)
self.stopEditing()
@webApi() return True
@api()
def unsuspend(self, cards): def unsuspend(self, cards):
self.suspend(cards, False) self.suspend(cards, False)
@webApi() @api()
def isSuspended(self, card): def suspended(self, card):
card = self.collection().getCard(card) card = self.collection().getCard(card)
return card.queue == -1 return card.queue == -1
@webApi() @api()
def areSuspended(self, cards): def areSuspended(self, cards):
suspended = [] suspended = []
for card in cards: for card in cards:
suspended.append(self.isSuspended(card)) suspended.append(self.suspended(card))
return suspended return suspended
@webApi() @api()
def areDue(self, cards): def areDue(self, cards):
due = [] due = []
for card in cards: for card in cards:
if self.findCards('cid:%s is:new' % card): if self.findCards('cid:{} is:new'.format(card)):
due.append(True) due.append(True)
continue else:
date, ivl = self.collection().db.all('select id/1000.0, ivl from revlog where cid = ?', card)[-1] date, ivl = self.collection().db.all('select id/1000.0, ivl from revlog where cid = ?', card)[-1]
if (ivl >= -1200): if ivl >= -1200:
if self.findCards('cid:%s is:due' % card): duo.append(bool(self.findCards('cid:{} is:due'.format(card))))
due.append(True)
else: else:
due.append(False) due.append(date - ivl <= time())
else:
if date - ivl <= time():
due.append(True)
else:
due.append(False)
return due return due
@webApi() @api()
def getIntervals(self, cards, complete=False): def getIntervals(self, cards, complete=False):
intervals = [] intervals = []
for card in cards: for card in cards:
if self.findCards('cid:%s is:new' % card): if self.findCards('cid:{} is:new'.format(card)):
intervals.append(0) intervals.append(0)
continue else:
interval = self.collection().db.list('select ivl from revlog where cid = ?', card) interval = self.collection().db.list('select ivl from revlog where cid = ?', card)
if not complete: if not complete:
interval = interval[-1] interval = interval[-1]
intervals.append(interval) intervals.append(interval)
return intervals return intervals
@webApi() @api()
def multi(self, actions): def multi(self, actions):
response = [] response = []
for item in actions: for item in actions:
response.append(self.handler(item)) response.append(self.handler(item))
return response return response
@webApi() @api()
def modelNames(self): def modelNames(self):
collection = self.collection() return self.collection().models.allNames()
if collection is not None:
return collection.models.allNames()
@webApi() @api()
def modelNamesAndIds(self): def modelNamesAndIds(self):
models = {} models = {}
for model in self.modelNames():
modelNames = self.modelNames() models[model] = int(self.collection().models.byName(model)['id'])
for model in modelNames:
mid = self.collection().models.byName(model)['id']
mid = int(mid) # sometimes Anki stores the ID as a string
models[model] = mid
return models return models
@webApi() @api()
def modelNameFromId(self, modelId): def modelNameFromId(self, modelId):
collection = self.collection() model = self.collection().models.get(modelId)
if collection is not None: if model is None:
model = collection.models.get(modelId) raise Exception('model was not found: {}'.format(modelId))
if model is not None: else:
return model['name'] return model['name']
@webApi() @api()
def modelFieldNames(self, modelName): def modelFieldNames(self, modelName):
collection = self.collection() model = self.collection().models.byName(modelName)
if collection is not None: if model is None:
model = collection.models.byName(modelName) raise Exception('model was not found: {}'.format(modelName))
if model is not None: else:
return [field['name'] for field in model['flds']] return [field['name'] for field in model['flds']]
@webApi() @api()
def modelFieldsOnTemplates(self, modelName): def modelFieldsOnTemplates(self, modelName):
model = self.collection().models.byName(modelName) model = self.collection().models.byName(modelName)
if model is None:
raise Exception('model was not found: {}'.format(modelName))
if model is not None:
templates = {} templates = {}
for template in model['tmpls']: for template in model['tmpls']:
fields = [] fields = []
for side in ['qfmt', 'afmt']: for side in ['qfmt', 'afmt']:
fieldsForSide = [] fieldsForSide = []
@ -723,7 +645,6 @@ class AnkiConnect:
continue continue
fieldsForSide.append(match) fieldsForSide.append(match)
fields.append(fieldsForSide) fields.append(fieldsForSide)
templates[template['name']] = fields templates[template['name']] = fields
@ -731,111 +652,111 @@ class AnkiConnect:
return templates return templates
@webApi() @api()
def getDeckConfig(self, deck): def getDeckConfig(self, deck):
if not deck in self.deckNames(): if not deck in self.deckNames():
return False return False
did = self.collection().decks.id(deck) collection = self.collection()
return self.collection().decks.confForDid(did) did = collection.decks.id(deck)
return collection.decks.confForDid(did)
@webApi() @api()
def saveDeckConfig(self, config): def saveDeckConfig(self, config):
configId = str(config['id']) collection = self.collection()
if not configId in self.collection().decks.dconf:
config['id'] = str(config['id'])
config['mod'] = anki.utils.intTime()
config['usn'] = collection.usn()
if not config['id'] in collection.decks.dconf:
return False return False
mod = anki.utils.intTime() collection.decks.dconf[config['id']] = config
usn = self.collection().usn() collection.decks.changed = True
config['mod'] = mod
config['usn'] = usn
self.collection().decks.dconf[configId] = config
self.collection().decks.changed = True
return True return True
@webApi() @api()
def setDeckConfigId(self, decks, configId): def setDeckConfigId(self, decks, configId):
configId = str(configId)
for deck in decks: for deck in decks:
if not deck in self.deckNames(): if not deck in self.deckNames():
return False return False
if not str(configId) in self.collection().decks.dconf: collection = self.collection()
if not configId in collection.decks.dconf:
return False return False
for deck in decks: for deck in decks:
did = str(self.collection().decks.id(deck)) did = str(collection.decks.id(deck))
aqt.mw.col.decks.decks[did]['conf'] = configId aqt.mw.col.decks.decks[did]['conf'] = configId
return True return True
@webApi() @api()
def cloneDeckConfigId(self, name, cloneFrom=1): def cloneDeckConfigId(self, name, cloneFrom='1'):
if not str(cloneFrom) in self.collection().decks.dconf: configId = str(cloneFrom)
if not configId in self.collection().decks.dconf:
return False return False
cloneFrom = self.collection().decks.getConf(cloneFrom) config = self.collection().decks.getConf(configId)
return self.collection().decks.confId(name, cloneFrom) return self.collection().decks.confId(name, config)
@webApi() @api()
def removeDeckConfigId(self, configId): def removeDeckConfigId(self, configId):
if configId == 1 or not str(configId) in self.collection().decks.dconf: configId = str(configId)
collection = self.collection()
if configId == 1 or not configId in collection.decks.dconf:
return False return False
self.collection().decks.remConf(configId) collection.decks.remConf(configId)
return True return True
@webApi() @api()
def deckNames(self): def deckNames(self):
collection = self.collection() return self.collection().decks.allNames()
if collection is not None:
return collection.decks.allNames()
@webApi() @api()
def deckNamesAndIds(self): def deckNamesAndIds(self):
decks = {} decks = {}
for deck in self.deckNames():
deckNames = self.deckNames() decks[deck] = self.collection().decks.id(deck)
for deck in deckNames:
did = self.collection().decks.id(deck)
decks[deck] = did
return decks return decks
@webApi() @api()
def deckNameFromId(self, deckId): def deckNameFromId(self, deckId):
collection = self.collection() deck = self.collection().decks.get(deckId)
if collection is not None: if deck is None:
deck = collection.decks.get(deckId) raise Exception('deck was not found: {}'.format(deckId))
if deck is not None: else:
return deck['name'] return deck['name']
@webApi() @api()
def findNotes(self, query=None): def findNotes(self, query=None):
if query is not None: if query is None:
return []
else:
return self.collection().findNotes(query) return self.collection().findNotes(query)
else:
return []
@webApi() @api()
def findCards(self, query=None): def findCards(self, query=None):
if query is not None: if query not None:
return self.collection().findCards(query)
else:
return [] return []
else:
return self.collection().findCards(query)
@webApi() @api()
def cardsInfo(self, cards): def cardsInfo(self, cards):
result = [] result = []
for cid in cards: for cid in cards:
@ -874,7 +795,7 @@ class AnkiConnect:
return result return result
@webApi() @api()
def notesInfo(self, notes): def notesInfo(self, notes):
result = [] result = []
for nid in notes: for nid in notes:
@ -893,8 +814,7 @@ class AnkiConnect:
'tags' : note.tags, 'tags' : note.tags,
'fields': fields, 'fields': fields,
'modelName': model['name'], 'modelName': model['name'],
'cards': self.collection().db.list( 'cards': self.collection().db.list('select id from cards where nid = ? order by ord', note.id)
'select id from cards where nid = ? order by ord', note.id)
}) })
except TypeError as e: except TypeError as e:
# Anki will give a TypeError if the note ID does not exist. # Anki will give a TypeError if the note ID does not exist.
@ -902,15 +822,17 @@ class AnkiConnect:
# returned result, so that the items of the input and return # returned result, so that the items of the input and return
# lists correspond. # lists correspond.
result.append({}) result.append({})
return result return result
@webApi() @api()
def getDecks(self, cards): def getDecks(self, cards):
decks = {} decks = {}
collection = self.collection()
for card in cards: for card in cards:
did = self.collection().db.scalar('select did from cards where id = ?', card) did = collection.db.scalar('select did from cards where id = ?', card)
deck = self.collection().decks.get(did)['name'] deck = collection.decks.get(did)['name']
if deck in decks: if deck in decks:
decks[deck].append(card) decks[deck].append(card)
@ -920,7 +842,7 @@ class AnkiConnect:
return decks return decks
@webApi() @api()
def createDeck(self, deck): def createDeck(self, deck):
self.startEditing() self.startEditing()
deckId = self.collection().decks.id(deck) deckId = self.collection().decks.id(deck)
@ -929,7 +851,7 @@ class AnkiConnect:
return deckId return deckId
@webApi() @api()
def changeDeck(self, cards, deck): def changeDeck(self, cards, deck):
self.startEditing() self.startEditing()
@ -947,7 +869,7 @@ class AnkiConnect:
self.stopEditing() self.stopEditing()
@webApi() @api()
def deleteDecks(self, decks, cardsToo=False): def deleteDecks(self, decks, cardsToo=False):
self.startEditing() self.startEditing()
for deck in decks: for deck in decks:
@ -956,12 +878,12 @@ class AnkiConnect:
self.stopEditing() self.stopEditing()
@webApi() @api()
def cardsToNotes(self, cards): def cardsToNotes(self, cards):
return self.collection().db.list('select distinct nid from cards where id in ' + anki.utils.ids2str(cards)) return self.collection().db.list('select distinct nid from cards where id in ' + anki.utils.ids2str(cards))
@webApi() @api()
def guiBrowse(self, query=None): def guiBrowse(self, query=None):
browser = aqt.dialogs.open('Browser', self.window()) browser = aqt.dialogs.open('Browser', self.window())
browser.activateWindow() browser.activateWindow()
@ -976,18 +898,18 @@ class AnkiConnect:
return browser.model.cards return browser.model.cards
@webApi() @api()
def guiAddCards(self): def guiAddCards(self):
addCards = aqt.dialogs.open('AddCards', self.window()) addCards = aqt.dialogs.open('AddCards', self.window())
addCards.activateWindow() addCards.activateWindow()
@webApi() @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'
@webApi() @api()
def guiCurrentCard(self): def guiCurrentCard(self):
if not self.guiReviewActive(): if not self.guiReviewActive():
raise Exception('Gui review is not currently active.') raise Exception('Gui review is not currently active.')
@ -1017,7 +939,7 @@ class AnkiConnect:
} }
@webApi() @api()
def guiStartCardTimer(self): def guiStartCardTimer(self):
if not self.guiReviewActive(): if not self.guiReviewActive():
return False return False
@ -1031,7 +953,7 @@ class AnkiConnect:
return False return False
@webApi() @api()
def guiShowQuestion(self): def guiShowQuestion(self):
if self.guiReviewActive(): if self.guiReviewActive():
self.reviewer()._showQuestion() self.reviewer()._showQuestion()
@ -1040,7 +962,7 @@ class AnkiConnect:
return False return False
@webApi() @api()
def guiShowAnswer(self): def guiShowAnswer(self):
if self.guiReviewActive(): if self.guiReviewActive():
self.window().reviewer._showAnswer() self.window().reviewer._showAnswer()
@ -1049,7 +971,7 @@ class AnkiConnect:
return False return False
@webApi() @api()
def guiAnswerCard(self, ease): def guiAnswerCard(self, ease):
if not self.guiReviewActive(): if not self.guiReviewActive():
return False return False
@ -1064,7 +986,7 @@ class AnkiConnect:
return True return True
@webApi() @api()
def guiDeckOverview(self, name): def guiDeckOverview(self, name):
collection = self.collection() collection = self.collection()
if collection is not None: if collection is not None:
@ -1077,12 +999,12 @@ class AnkiConnect:
return False return False
@webApi() @api()
def guiDeckBrowser(self): def guiDeckBrowser(self):
self.window().moveToState('deckBrowser') self.window().moveToState('deckBrowser')
@webApi() @api()
def guiDeckReview(self, name): def guiDeckReview(self, name):
if self.guiDeckOverview(name): if self.guiDeckOverview(name):
self.window().moveToState('review') self.window().moveToState('review')
@ -1091,7 +1013,7 @@ class AnkiConnect:
return False return False
@webApi() @api()
def guiExitAnki(self): def guiExitAnki(self):
timer = QTimer() timer = QTimer()
def exitAnki(): def exitAnki():
@ -1101,12 +1023,12 @@ class AnkiConnect:
timer.start(1000) # 1s should be enough to allow the response to be sent. timer.start(1000) # 1s should be enough to allow the response to be sent.
@webApi() @api()
def sync(self): def sync(self):
self.window().onSync() self.window().onSync()
@webApi() @api()
def upgrade(self): def upgrade(self):
response = QMessageBox.question( response = QMessageBox.question(
self.window(), self.window(),
@ -1129,12 +1051,12 @@ class AnkiConnect:
return False return False
@webApi() @api()
def version(self): def version(self):
return API_VERSION return API_VERSION
@webApi() @api()
def addNotes(self, notes): def addNotes(self, notes):
results = [] results = []
for note in notes: for note in notes:
@ -1146,7 +1068,7 @@ class AnkiConnect:
return results return results
@webApi() @api()
def canAddNotes(self, notes): def canAddNotes(self, notes):
results = [] results = []
for note in notes: for note in notes: