From 6e7938a93cb50c9e794edeee359b262521c73377 Mon Sep 17 00:00:00 2001 From: Alex Yatskov Date: Sun, 1 May 2016 18:42:17 -0700 Subject: [PATCH] Work on remote API --- yomi_base/ajax.py | 30 +++++++-------- yomi_base/anki_bridge.py | 4 +- yomi_base/preferences.py | 16 ++++---- yomi_base/reader.py | 36 ++++++++++-------- yomi_base/remote_api.py | 80 ++++++++++++++++++++++++++++++++++++++++ yomichan.py | 5 ++- 6 files changed, 129 insertions(+), 42 deletions(-) create mode 100644 yomi_base/remote_api.py diff --git a/yomi_base/ajax.py b/yomi_base/ajax.py index 7079fa9..3e55f59 100644 --- a/yomi_base/ajax.py +++ b/yomi_base/ajax.py @@ -29,14 +29,14 @@ class AjaxRequest: class AjaxClient: - def __init__(self, sock, callback): + def __init__(self, sock, handler): self.sock = sock - self.callback = callback - self.readBuff = '' + self.handler = handler self.readBuff = '' + self.writeBuff = '' - def advance(self, recvSize=1): + def advance(self, recvSize=1024): if self.sock is None: return False @@ -53,12 +53,12 @@ class AjaxClient: req, length = self.parseRequest(self.readBuff) if req is not None: self.readBuff = self.readBuff[length:] - self.readBuff += self.callback(req) + self.writeBuff += self.handler(req) - if wlist and self.readBuff: - length = self.sock.send(self.readBuff) - self.readBuff = self.readBuff[length:] - if not self.readBuff: + if wlist and self.writeBuff: + length = self.sock.send(self.writeBuff) + self.writeBuff = self.writeBuff[length:] + if not self.writeBuff: self.close() return False @@ -71,7 +71,7 @@ class AjaxClient: self.sock = None self.readBuff = '' - self.readBuff = '' + self.writeBuff = '' def parseRequest(self, data): @@ -96,8 +96,8 @@ class AjaxClient: class AjaxServer: - def __init__(self, callback): - self.callback = callback + def __init__(self, handler): + self.handler = handler self.clients = [] self.sock = None @@ -116,7 +116,7 @@ class AjaxServer: clientSock = self.sock.accept()[0] if clientSock is not None: clientSock.setblocking(False) - self.clients.append(AjaxClient(clientSock, self.callbackWrapper)) + self.clients.append(AjaxClient(clientSock, self.handlerWrapper)) def advanceClients(self): @@ -133,8 +133,8 @@ class AjaxServer: self.sock.listen(backlog) - def callbackWrapper(self, req): - body = json.dumps(self.callback(json.loads(req.body))) + def handlerWrapper(self, req): + body = json.dumps(self.handler(json.loads(req.body))) resp = '' headers = { diff --git a/yomi_base/anki_bridge.py b/yomi_base/anki_bridge.py index 48e4090..8ac06c1 100644 --- a/yomi_base/anki_bridge.py +++ b/yomi_base/anki_bridge.py @@ -21,7 +21,7 @@ import aqt class Anki: - def addNote(self, deckName, modelName, fields, tags=list()): + def addNote(self, deckName, modelName, fields, tags=[]): note = self.createNote(deckName, modelName, fields, tags) if note is not None: collection = self.collection() @@ -35,7 +35,7 @@ class Anki: return bool(self.createNote(deckName, modelName, fields)) - def createNote(self, deckName, modelName, fields, tags=list()): + def createNote(self, deckName, modelName, fields, tags=[]): model = self.models().byName(modelName) if model is None: return None diff --git a/yomi_base/preferences.py b/yomi_base/preferences.py index 2b7f93b..20d191e 100644 --- a/yomi_base/preferences.py +++ b/yomi_base/preferences.py @@ -65,15 +65,15 @@ class DialogPreferences(QtGui.QDialog, gen.preferences_ui.Ui_DialogPreferences): def dialogToData(self): - self.preferences['checkForUpdates'] = self.checkCheckForUpdates.isChecked() + self.preferences['checkForUpdates'] = self.checkCheckForUpdates.isChecked() self.preferences['rememberTextContent'] = self.checkRememberTextContent.isChecked() - self.preferences['allowEditing'] = self.checkAllowEditing.isChecked() - self.preferences['loadRecentFile'] = self.checkLoadRecentFile.isChecked() - self.preferences['maxResults'] = self.spinMaxResults.value() - self.preferences['scanLength'] = self.spinScanLength.value() - self.preferences['stripReadings'] = self.checkStripReadings.isChecked() - self.preferences['enableRemoteApi'] = self.checkEnableRemoteApi.isChecked() - self.preferences['firstRun'] = False + self.preferences['allowEditing'] = self.checkAllowEditing.isChecked() + self.preferences['loadRecentFile'] = self.checkLoadRecentFile.isChecked() + self.preferences['maxResults'] = self.spinMaxResults.value() + self.preferences['scanLength'] = self.spinScanLength.value() + self.preferences['stripReadings'] = self.checkStripReadings.isChecked() + self.preferences['enableRemoteApi'] = self.checkEnableRemoteApi.isChecked() + self.preferences['firstRun'] = False if self.anki is not None: self.dialogToProfile() diff --git a/yomi_base/reader.py b/yomi_base/reader.py index e5711ca..0d9af34 100644 --- a/yomi_base/reader.py +++ b/yomi_base/reader.py @@ -30,30 +30,31 @@ import updates class MainWindowReader(QtGui.QMainWindow, gen.reader_ui.Ui_MainWindowReader): class State: def __init__(self): - self.filename = unicode() - self.kanjiDefs = list() - self.scanPosition = 0 + self.filename = unicode() + self.kanjiDefs = [] + self.scanPosition = 0 self.searchPosition = 0 - self.searchText = unicode() - self.vocabDefs = list() + self.searchText = unicode() + self.vocabDefs = [] - def __init__(self, parent, preferences, language, filename=None, anki=None, closed=None): + def __init__(self, parent, preferences, language, filename=None, anki=None, remoteApi=None, closed=None): QtGui.QMainWindow.__init__(self, parent) self.setupUi(self) - self.textContent.mouseMoveEvent = self.onContentMouseMove + self.textContent.mouseMoveEvent = self.onContentMouseMove self.textContent.mousePressEvent = self.onContentMousePress self.dockAnki.setEnabled(anki is not None) - self.facts = list() - self.anki = anki - self.closed = closed - self.language = language + self.facts = [] + self.anki = anki + self.remoteApi = remoteApi + self.closed = closed + self.language = language self.preferences = preferences - self.state = self.State() - self.updates = updates.UpdateFinder() - self.zoom = 0 + self.state = self.State() + self.updates = updates.UpdateFinder() + self.zoom = 0 self.applyPreferences() self.updateRecentFiles() @@ -103,6 +104,9 @@ class MainWindowReader(QtGui.QMainWindow, gen.reader_ui.Ui_MainWindowReader): if self.preferences['windowSize'] is not None: self.resize(QtCore.QSize(*self.preferences['windowSize'])) + if self.remoteApi is not None: + self.remoteApi.enable(self.preferences['enableRemoteApi']) + self.comboTags.addItems(self.preferences['tags']) self.applyPreferencesContent() @@ -587,8 +591,8 @@ class MainWindowReader(QtGui.QMainWindow, gen.reader_ui.Ui_MainWindowReader): def importWordList(self, words): - self.state.vocabDefs = list() - self.state.kanjiDefs = list() + self.state.vocabDefs = [] + self.state.kanjiDefs = [] for word in words: if self.dockVocab.isVisible(): diff --git a/yomi_base/remote_api.py b/yomi_base/remote_api.py new file mode 100644 index 0000000..68a2582 --- /dev/null +++ b/yomi_base/remote_api.py @@ -0,0 +1,80 @@ +# -*- coding: utf-8 -*- + +# Copyright (C) 2016 Alex Yatskov +# Author: Alex Yatskov +# +# 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 +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . + + +from PyQt4 import QtCore +from ajax import AjaxServer +import constants + + +class RemoteApi: + def __init__(self, anki, enabled, interval=50): + self.server = None + self.enable(enabled) + self.timer = QtCore.QTimer() + self.timer.timeout.connect(self.advance) + self.timer.start(interval) + + self.handlers = { + 'addNote': self.apiAddNote, + 'apiCanAddNote': self.apiCanAddNote, + 'getVersion': self.apiGetVersion, + } + + + def enable(self, enabled=True): + if self.server is None and enabled: + self.server = AjaxServer(self.handler) + self.server.listen() + elif self.server is not None and not enabled: + self.server.close() + self.server = None + + + def advance(self): + if self.server is not None: + self.server.advance() + + + def handler(self, request): + self.handlers.get(request.get('action'), self.apiInvalidRequest)(request.get('data')) + + + def apiAddNote(self, data): + deckName = data.get('deckName', unicode()) + modelName = data.get('modelName', unicode()) + fields = data.get('fields', {}) + tags = data.get('tags', []) + + return self.anki.addNote(deckName, modelName, fields, tags) + + + def apiCanAddNote(self, data): + deckName = data.get('deckName', unicode()) + modelName = data.get('modelName', unicode()) + fields = data.get('fields', {}) + + return self.anki.canAddNote(deckName, modelName, fields) + + + def apiGetVersion(self, data): + return {'version': constants.c['appVersion']} + + + def apiInvalidRequest(self, data): + return {} diff --git a/yomichan.py b/yomichan.py index 7c3ebe9..6267e00 100755 --- a/yomichan.py +++ b/yomichan.py @@ -17,10 +17,11 @@ # along with this program. If not, see . -from PyQt4 import QtGui, QtCore +from PyQt4 import QtGui from yomi_base import japanese from yomi_base.preference_data import Preferences from yomi_base.reader import MainWindowReader +from yomi_base.remote_api import RemoteApi import sys @@ -39,6 +40,7 @@ class YomichanPlugin(Yomichan): self.window = None self.anki = anki_bridge.Anki() self.parent = self.anki.window() + self.remoteApi = RemoteApi(self.anki, self.preferences['enableRemoteApi']) separator = QtGui.QAction(self.parent) separator.setSeparator(True) @@ -62,6 +64,7 @@ class YomichanPlugin(Yomichan): self.language, None, self.anki, + self.remoteApi, self.onWindowClose ) self.window.show()