Implement cancelling python coros from QML

This was needed to implement the cancel button featue on the login
screen
This commit is contained in:
miruka 2019-12-07 18:33:33 -04:00
parent 4a93a24f74
commit 6f589dbda5
3 changed files with 66 additions and 15 deletions

View File

@ -54,7 +54,7 @@ class App:
return asyncio.run_coroutine_threadsafe(coro, self.loop) return asyncio.run_coroutine_threadsafe(coro, self.loop)
def _call_coro(self, coro: Coroutine, uuid: str) -> None: def _call_coro(self, coro: Coroutine, uuid: str) -> Future:
def on_done(future: Future) -> None: def on_done(future: Future) -> None:
result = exception = trace = None result = exception = trace = None
@ -66,21 +66,23 @@ class App:
CoroutineDone(uuid, result, exception, trace) CoroutineDone(uuid, result, exception, trace)
self.run_in_loop(coro).add_done_callback(on_done) future = self.run_in_loop(coro)
future.add_done_callback(on_done)
return future
def call_backend_coro(self, name: str, uuid: str, args: Sequence[str] = (), def call_backend_coro(self, name: str, uuid: str, args: Sequence[str] = (),
) -> None: ) -> Future:
self._call_coro(attrgetter(name)(self.backend)(*args), uuid) return self._call_coro(attrgetter(name)(self.backend)(*args), uuid)
def call_client_coro(self, def call_client_coro(self,
account_id: str, account_id: str,
name: str, name: str,
uuid: str, uuid: str,
args: Sequence[str] = ()) -> None: args: Sequence[str] = ()) -> Future:
client = self.backend.clients[account_id] client = self.backend.clients[account_id]
self._call_coro(attrgetter(name)(client)(*args), uuid) return self._call_coro(attrgetter(name)(client)(*args), uuid)
def pdb(self, additional_data: Sequence = ()) -> None: def pdb(self, additional_data: Sequence = ()) -> None:

View File

@ -31,7 +31,7 @@ HBox {
signInTimeout.restart() signInTimeout.restart()
py.callCoro("login_client", args, userId => { loginFuture = py.callCoro("login_client", args, userId => {
signInTimeout.stop() signInTimeout.stop()
errorMessage.text = "" errorMessage.text = ""
button.loading = false button.loading = false
@ -48,9 +48,14 @@ HBox {
) )
}, type => { }, type => {
if (type === "CancelledError") return
signInTimeout.stop() signInTimeout.stop()
if (type === "CancelledError") {
loginFuture = null
button.loading = false
return
}
let txt = qsTr("Invalid request or login type") let txt = qsTr("Invalid request or login type")
if (type === "MatrixForbidden") if (type === "MatrixForbidden")
@ -64,11 +69,14 @@ HBox {
}) })
}, },
cancel: button => {} cancel: button => { if (loginFuture) loginFuture.cancel() }
}) })
property var loginFuture: null
property string signInWith: "username" property string signInWith: "username"
readonly property bool canSignIn: readonly property bool canSignIn:
serverField.text && idField.text && passwordField.text && serverField.text && idField.text && passwordField.text &&
! serverField.error ! serverField.error

View File

@ -1,15 +1,40 @@
import QtQuick 2.12 import QtQuick 2.12
import QtQuick.Controls 2.12
import io.thp.pyotherside 1.5 import io.thp.pyotherside 1.5
import "event_handlers.js" as EventHandlers import "event_handlers.js" as EventHandlers
Python { Python {
id: py id: py
property bool ready: false property bool ready: false
property bool startupAnyAccountsSaved: false property bool startupAnyAccountsSaved: false
property var pendingCoroutines: ({}) property var pendingCoroutines: ({})
function newQmlFuture() {
return {
_pyFuture: null,
get pyFuture() { return this._pyFuture },
set pyFuture(value) {
this._pyFuture = value
if (this.cancelPending) this.cancel()
},
cancelPending: false,
cancel: function() {
if (! this.pyFuture) {
this.cancelPending = true
return
}
py.call(py.getattr(this.pyFuture, "cancel"))
},
}
}
function setattr(obj, attr, value, callback=null) { function setattr(obj, attr, value, callback=null) {
py.call(py.getattr(obj, "__setattr__"), [attr, value], callback) py.call(py.getattr(obj, "__setattr__"), [attr, value], callback)
} }
@ -22,27 +47,43 @@ Python {
let uuid = name + "." + CppUtils.uuid() let uuid = name + "." + CppUtils.uuid()
pendingCoroutines[uuid] = {onSuccess, onError} pendingCoroutines[uuid] = {onSuccess, onError}
call("APP.call_backend_coro", [name, uuid, args])
let qmlFuture = py.newQmlFuture()
call("APP.call_backend_coro", [name, uuid, args], pyFuture => {
qmlFuture.pyFuture = pyFuture
})
return qmlFuture
} }
function callClientCoro( function callClientCoro(
accountId, name, args=[], onSuccess=null, onError=null accountId, name, args=[], onSuccess=null, onError=null
) { ) {
let qmlFuture = py.newQmlFuture()
callCoro("wait_until_client_exists", [accountId], () => { callCoro("wait_until_client_exists", [accountId], () => {
let uuid = accountId + "." + name + "." + CppUtils.uuid() let uuid = accountId + "." + name + "." + CppUtils.uuid()
pendingCoroutines[uuid] = {onSuccess, onError} pendingCoroutines[uuid] = {onSuccess, onError}
call("APP.call_client_coro", [accountId, name, uuid, args])
let call_args = [accountId, name, uuid, args]
call("APP.call_client_coro", call_args, pyFuture => {
qmlFuture.pyFuture = pyFuture
})
}) })
return qmlFuture
} }
function saveConfig(backend_attribute, data, callback=null) { function saveConfig(backend_attribute, data, callback=null) {
if (! py.ready) { return } // config not loaded yet if (! py.ready) { return } // config not loaded yet
callCoro(backend_attribute + ".write", [data], callback) return callCoro(backend_attribute + ".write", [data], callback)
} }
function loadSettings(callback=null) { function loadSettings(callback=null) {
callCoro("load_settings", [], ([settings, uiState, theme]) => { return callCoro("load_settings", [], ([settings, uiState, theme]) => {
window.settings = settings window.settings = settings
window.uiState = uiState window.uiState = uiState
window.theme = Qt.createQmlObject(theme, window, "theme") window.theme = Qt.createQmlObject(theme, window, "theme")