Don't pass Python Future objects to QML
Returning a Future doesn't work on Windows for some reason (https://github.com/thp/pyotherside/issues/116). Instead of using these objects from QML to cancel running coroutines, call a Python QMLBridge function that takes a coroutine UUID and will take care of the cancelling.
This commit is contained in:
parent
53d2ab17af
commit
ee1091b4dc
2
TODO.md
2
TODO.md
|
@ -1,5 +1,7 @@
|
||||||
# TODO
|
# TODO
|
||||||
|
|
||||||
|
- Encrypted rooms don't show invites in member list after Mirage restart
|
||||||
|
- Room display name not updated when someone removes theirs
|
||||||
- Fix right margin of own `<image url>\n<image url>` messages
|
- Fix right margin of own `<image url>\n<image url>` messages
|
||||||
|
|
||||||
- filter > enter > room list is always scrolled to top
|
- filter > enter > room list is always scrolled to top
|
||||||
|
|
|
@ -21,7 +21,7 @@ import traceback
|
||||||
from concurrent.futures import Future
|
from concurrent.futures import Future
|
||||||
from operator import attrgetter
|
from operator import attrgetter
|
||||||
from threading import Thread
|
from threading import Thread
|
||||||
from typing import Coroutine, Sequence
|
from typing import Coroutine, Dict, Sequence
|
||||||
|
|
||||||
import pyotherside
|
import pyotherside
|
||||||
|
|
||||||
|
@ -52,6 +52,8 @@ class QMLBridge:
|
||||||
from .backend import Backend
|
from .backend import Backend
|
||||||
self.backend: Backend = Backend()
|
self.backend: Backend = Backend()
|
||||||
|
|
||||||
|
self._running_futures: Dict[str, Future] = {}
|
||||||
|
|
||||||
Thread(target=self._start_asyncio_loop).start()
|
Thread(target=self._start_asyncio_loop).start()
|
||||||
|
|
||||||
|
|
||||||
|
@ -73,7 +75,7 @@ class QMLBridge:
|
||||||
self._loop.run_forever()
|
self._loop.run_forever()
|
||||||
|
|
||||||
|
|
||||||
def _call_coro(self, coro: Coroutine, uuid: str) -> Future:
|
def _call_coro(self, coro: Coroutine, uuid: str) -> None:
|
||||||
"""Schedule a coroutine to run in our thread and return a `Future`."""
|
"""Schedule a coroutine to run in our thread and return a `Future`."""
|
||||||
|
|
||||||
def on_done(future: Future) -> None:
|
def on_done(future: Future) -> None:
|
||||||
|
@ -87,27 +89,37 @@ class QMLBridge:
|
||||||
trace = traceback.format_exc().rstrip()
|
trace = traceback.format_exc().rstrip()
|
||||||
|
|
||||||
CoroutineDone(uuid, result, exception, trace)
|
CoroutineDone(uuid, result, exception, trace)
|
||||||
|
del self._running_futures[uuid]
|
||||||
|
|
||||||
future = asyncio.run_coroutine_threadsafe(coro, self._loop)
|
future = asyncio.run_coroutine_threadsafe(coro, self._loop)
|
||||||
|
self._running_futures[uuid] = future
|
||||||
future.add_done_callback(on_done)
|
future.add_done_callback(on_done)
|
||||||
return future
|
|
||||||
|
|
||||||
|
|
||||||
def call_backend_coro(
|
def call_backend_coro(
|
||||||
self, name: str, uuid: str, args: Sequence[str] = (),
|
self, name: str, uuid: str, args: Sequence[str] = (),
|
||||||
) -> Future:
|
) -> None:
|
||||||
"""Schedule a `Backend` coroutine and return a `Future`."""
|
"""Schedule a coroutine from the `QMLBridge.backend` object."""
|
||||||
|
|
||||||
return self._call_coro(attrgetter(name)(self.backend)(*args), uuid)
|
self._call_coro(attrgetter(name)(self.backend)(*args), uuid)
|
||||||
|
|
||||||
|
|
||||||
def call_client_coro(
|
def call_client_coro(
|
||||||
self, user_id: str, name: str, uuid: str, args: Sequence[str] = (),
|
self, user_id: str, name: str, uuid: str, args: Sequence[str] = (),
|
||||||
) -> Future:
|
) -> None:
|
||||||
"""Schedule a `MatrixClient` coroutine and return a `Future`."""
|
"""Schedule a coroutine from a `QMLBridge.backend.clients` client."""
|
||||||
|
|
||||||
client = self.backend.clients[user_id]
|
client = self.backend.clients[user_id]
|
||||||
return self._call_coro(attrgetter(name)(client)(*args), uuid)
|
self._call_coro(attrgetter(name)(client)(*args), uuid)
|
||||||
|
|
||||||
|
|
||||||
|
def cancel_coro(self, uuid: str) -> None:
|
||||||
|
"""Cancel a couroutine scheduled by the `QMLBridge` methods."""
|
||||||
|
|
||||||
|
try:
|
||||||
|
self._running_futures[uuid].cancel()
|
||||||
|
except KeyError:
|
||||||
|
log.warning("Couldn't cancel coroutine %s, future not found", uuid)
|
||||||
|
|
||||||
|
|
||||||
def pdb(self, additional_data: Sequence = ()) -> None:
|
def pdb(self, additional_data: Sequence = ()) -> None:
|
||||||
|
|
|
@ -2,7 +2,6 @@
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||||
|
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import "../PythonBridge"
|
|
||||||
|
|
||||||
HImage {
|
HImage {
|
||||||
id: image
|
id: image
|
||||||
|
@ -18,7 +17,7 @@ HImage {
|
||||||
property bool canUpdate: true
|
property bool canUpdate: true
|
||||||
property bool show: ! canUpdate
|
property bool show: ! canUpdate
|
||||||
|
|
||||||
property Future getFuture: null
|
property string getFutureId: ""
|
||||||
|
|
||||||
readonly property bool isMxc: mxc.startsWith("mxc://")
|
readonly property bool isMxc: mxc.startsWith("mxc://")
|
||||||
|
|
||||||
|
@ -45,10 +44,11 @@ HImage {
|
||||||
[clientUserId, image.mxc, image.title, w, h, cryptDict] :
|
[clientUserId, image.mxc, image.title, w, h, cryptDict] :
|
||||||
[clientUserId, image.mxc, image.title, cryptDict]
|
[clientUserId, image.mxc, image.title, cryptDict]
|
||||||
|
|
||||||
getFuture = py.callCoro("media_cache." + method, args, path => {
|
getFutureId = py.callCoro("media_cache." + method, args, path => {
|
||||||
if (! image) return
|
if (! image) return
|
||||||
if (image.cachedPath !== path) image.cachedPath = path
|
if (image.cachedPath !== path) image.cachedPath = path
|
||||||
|
|
||||||
|
getFutureId = ""
|
||||||
image.broken = Qt.binding(() => image.status === Image.Error)
|
image.broken = Qt.binding(() => image.status === Image.Error)
|
||||||
image.show = image.visible
|
image.show = image.visible
|
||||||
|
|
||||||
|
@ -68,5 +68,5 @@ HImage {
|
||||||
onHeightChanged: Qt.callLater(reload)
|
onHeightChanged: Qt.callLater(reload)
|
||||||
onVisibleChanged: Qt.callLater(reload)
|
onVisibleChanged: Qt.callLater(reload)
|
||||||
onMxcChanged: Qt.callLater(reload)
|
onMxcChanged: Qt.callLater(reload)
|
||||||
Component.onDestruction: if (getFuture) getFuture.cancel()
|
Component.onDestruction: if (getFutureId) py.cancelCoro(getFutureId)
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,11 +5,10 @@ import QtQuick 2.12
|
||||||
import QtQuick.Controls 2.12
|
import QtQuick.Controls 2.12
|
||||||
import Qt.labs.platform 1.1
|
import Qt.labs.platform 1.1
|
||||||
import "../Popups"
|
import "../Popups"
|
||||||
import "../PythonBridge"
|
|
||||||
|
|
||||||
HFileDialogOpener {
|
HFileDialogOpener {
|
||||||
property string userId: ""
|
property string userId: ""
|
||||||
property Future importFuture: null
|
property string importFutureId: ""
|
||||||
|
|
||||||
|
|
||||||
fill: false
|
fill: false
|
||||||
|
@ -28,13 +27,13 @@ HFileDialogOpener {
|
||||||
const call = py.callClientCoro
|
const call = py.callClientCoro
|
||||||
const path = file.toString().replace(/^file:\/\//, "")
|
const path = file.toString().replace(/^file:\/\//, "")
|
||||||
|
|
||||||
importFuture = call(userId, "import_keys", [path, pass], () => {
|
importFutureId = call(userId, "import_keys", [path, pass], () => {
|
||||||
importFuture = null
|
importFutureId = ""
|
||||||
callback(true)
|
callback(true)
|
||||||
|
|
||||||
}, (type, args, error, traceback, uuid) => {
|
}, (type, args, error, traceback, uuid) => {
|
||||||
let unknown = false
|
let unknown = false
|
||||||
importFuture = null
|
importFutureId = ""
|
||||||
|
|
||||||
callback(
|
callback(
|
||||||
type === "EncryptionError" ?
|
type === "EncryptionError" ?
|
||||||
|
@ -63,17 +62,17 @@ HFileDialogOpener {
|
||||||
}
|
}
|
||||||
|
|
||||||
summary.text:
|
summary.text:
|
||||||
importFuture ?
|
importFutureId ?
|
||||||
qsTr("This might take a while...") :
|
qsTr("This might take a while...") :
|
||||||
qsTr("Passphrase used to protect this file:")
|
qsTr("Passphrase used to protect this file:")
|
||||||
validateButton.text: qsTr("Import")
|
validateButton.text: qsTr("Import")
|
||||||
validateButton.icon.name: "import-keys"
|
validateButton.icon.name: "import-keys"
|
||||||
|
|
||||||
onClosed: if (importFuture) importFuture.cancel()
|
onClosed: if (importFutureId) py.cancelCoro(importFutureId)
|
||||||
|
|
||||||
Binding on closePolicy {
|
Binding on closePolicy {
|
||||||
value: Popup.CloseOnEscape
|
value: Popup.CloseOnEscape
|
||||||
when: importFuture
|
when: importFutureId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,13 +7,12 @@ import Clipboard 0.1
|
||||||
import ".."
|
import ".."
|
||||||
import "../Base"
|
import "../Base"
|
||||||
import "../Base/HTile"
|
import "../Base/HTile"
|
||||||
import "../PythonBridge"
|
|
||||||
|
|
||||||
HTile {
|
HTile {
|
||||||
id: room
|
id: room
|
||||||
|
|
||||||
property Future fetchProfilesFuture: null
|
property string fetchProfilesFutureId: ""
|
||||||
property Future loadEventsFuture: null
|
property string loadEventsFutureId: ""
|
||||||
property bool moreToLoad: true
|
property bool moreToLoad: true
|
||||||
|
|
||||||
readonly property bool joined: ! invited && ! parted
|
readonly property bool joined: ! invited && ! parted
|
||||||
|
@ -204,8 +203,8 @@ HTile {
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
if (fetchProfilesFuture) fetchProfilesFuture.cancel()
|
if (fetchProfilesFutureId) py.cancelCoro(fetchProfilesFutureId)
|
||||||
if (loadEventsFuture) loadEventsFuture.cancel()
|
if (loadEventsFutureId) py.cancelCoro(loadEventsFutureId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
|
@ -217,14 +216,14 @@ HTile {
|
||||||
! lastEvent &&
|
! lastEvent &&
|
||||||
moreToLoad
|
moreToLoad
|
||||||
|
|
||||||
onTriggered: if (! loadEventsFuture) {
|
onTriggered: if (! loadEventsFutureId) {
|
||||||
loadEventsFuture = py.callClientCoro(
|
loadEventsFutureId = py.callClientCoro(
|
||||||
model.for_account,
|
model.for_account,
|
||||||
"load_past_events",
|
"load_past_events",
|
||||||
[model.id],
|
[model.id],
|
||||||
more => {
|
more => {
|
||||||
if (! room) return // delegate was destroyed
|
if (! room) return // delegate was destroyed
|
||||||
loadEventsFuture = null
|
loadEventsFutureId = ""
|
||||||
moreToLoad = more
|
moreToLoad = more
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -242,13 +241,13 @@ HTile {
|
||||||
lastEvent.fetch_profile
|
lastEvent.fetch_profile
|
||||||
|
|
||||||
onTriggered: {
|
onTriggered: {
|
||||||
if (fetchProfilesFuture) fetchProfilesFuture.cancel()
|
if (fetchProfilesFutureId) py.cancelCoro(fetchProfilesFutureId)
|
||||||
|
|
||||||
fetchProfilesFuture = py.callClientCoro(
|
fetchProfilesFutureId = py.callClientCoro(
|
||||||
model.for_account,
|
model.for_account,
|
||||||
"get_event_profiles",
|
"get_event_profiles",
|
||||||
[model.id, lastEvent.id],
|
[model.id, lastEvent.id],
|
||||||
() => { if (room) fetchProfilesFuture = null },
|
() => { if (room) fetchProfilesFutureId = "" },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import QtQuick.Layouts 1.12
|
||||||
import "../.."
|
import "../.."
|
||||||
import "../../Base"
|
import "../../Base"
|
||||||
import "../../Base/Buttons"
|
import "../../Base/Buttons"
|
||||||
import "../../PythonBridge"
|
|
||||||
import "../../ShortcutBundles"
|
import "../../ShortcutBundles"
|
||||||
|
|
||||||
HColumnPage {
|
HColumnPage {
|
||||||
|
@ -18,19 +17,19 @@ HColumnPage {
|
||||||
property bool enableFlickShortcuts:
|
property bool enableFlickShortcuts:
|
||||||
SwipeView ? SwipeView.isCurrentItem : true
|
SwipeView ? SwipeView.isCurrentItem : true
|
||||||
|
|
||||||
property Future loadFuture: null
|
property string loadFutureId: ""
|
||||||
|
|
||||||
function takeFocus() {} // TODO
|
function takeFocus() {} // TODO
|
||||||
|
|
||||||
function loadDevices() {
|
function loadDevices() {
|
||||||
loadFuture = py.callClientCoro(userId, "devices_info", [], devices => {
|
loadFutureId = py.callClientCoro(userId, "devices_info",[],devices => {
|
||||||
deviceList.uncheckAll()
|
deviceList.uncheckAll()
|
||||||
deviceList.model.clear()
|
deviceList.model.clear()
|
||||||
|
|
||||||
for (const device of devices)
|
for (const device of devices)
|
||||||
deviceList.model.append(device)
|
deviceList.model.append(device)
|
||||||
|
|
||||||
loadFuture = null
|
loadFutureId = ""
|
||||||
deviceList.sectionItemCounts = getSectionItemCounts()
|
deviceList.sectionItemCounts = getSectionItemCounts()
|
||||||
|
|
||||||
if (page.enabled && ! deviceList.currentItem)
|
if (page.enabled && ! deviceList.currentItem)
|
||||||
|
@ -187,7 +186,7 @@ HColumnPage {
|
||||||
height: width
|
height: width
|
||||||
|
|
||||||
source: "../../Base/HBusyIndicator.qml"
|
source: "../../Base/HBusyIndicator.qml"
|
||||||
active: page.loadFuture
|
active: page.loadFutureId
|
||||||
opacity: active ? 1 : 0
|
opacity: active ? 1 : 0
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation { factor: 2 } }
|
Behavior on opacity { HNumberAnimation { factor: 2 } }
|
||||||
|
|
|
@ -21,26 +21,26 @@ HBox {
|
||||||
property var saveProperties: ["acceptedUserUrl", "knownHttps"]
|
property var saveProperties: ["acceptedUserUrl", "knownHttps"]
|
||||||
property string loadingIconStep: "server-ping-bad"
|
property string loadingIconStep: "server-ping-bad"
|
||||||
|
|
||||||
property Future connectFuture: null
|
property string connectFutureId: ""
|
||||||
property Future fetchServersFuture: null
|
property string fetchServersFutureId: ""
|
||||||
|
|
||||||
signal accepted()
|
signal accepted()
|
||||||
|
|
||||||
function takeFocus() { serverField.item.field.forceActiveFocus() }
|
function takeFocus() { serverField.item.field.forceActiveFocus() }
|
||||||
|
|
||||||
function fetchServers() {
|
function fetchServers() {
|
||||||
if (fetchServersFuture) fetchServersFuture.cancel()
|
if (fetchServersFutureId) py.cancelCoro(fetchServersFutureId)
|
||||||
|
|
||||||
fetchServersFuture = py.callCoro("fetch_homeservers", [], () => {
|
fetchServersFutureId = py.callCoro("fetch_homeservers", [], () => {
|
||||||
fetchServersFuture = null
|
fetchServersFutureId = ""
|
||||||
}, (type, args, error, traceback) => {
|
}, (type, args, error, traceback) => {
|
||||||
fetchServersFuture = null
|
fetchServersFutureId = ""
|
||||||
print( traceback) // TODO: display error graphically
|
print( traceback) // TODO: display error graphically
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function connect() {
|
function connect() {
|
||||||
if (connectFuture) connectFuture.cancel()
|
if (connectFutureId) py.cancelCoro(connectFutureId)
|
||||||
connectTimeout.restart()
|
connectTimeout.restart()
|
||||||
|
|
||||||
const typedUrl = serverField.item.field.cleanText
|
const typedUrl = serverField.item.field.cleanText
|
||||||
|
@ -49,10 +49,10 @@ HBox {
|
||||||
if (box.knownHttps)
|
if (box.knownHttps)
|
||||||
args[0] = args[0].replace(/^(https?:\/\/)?/, "https://")
|
args[0] = args[0].replace(/^(https?:\/\/)?/, "https://")
|
||||||
|
|
||||||
connectFuture = py.callCoro("server_info", args, ([url, flows]) => {
|
connectFutureId = py.callCoro("server_info", args, ([url, flows]) => {
|
||||||
connectTimeout.stop()
|
connectTimeout.stop()
|
||||||
serverField.errorLabel.text = ""
|
serverField.errorLabel.text = ""
|
||||||
connectFuture = null
|
connectFutureId = ""
|
||||||
|
|
||||||
if (! (
|
if (! (
|
||||||
flows.includes("m.login.password") ||
|
flows.includes("m.login.password") ||
|
||||||
|
@ -75,7 +75,7 @@ HBox {
|
||||||
console.error(traceback)
|
console.error(traceback)
|
||||||
|
|
||||||
connectTimeout.stop()
|
connectTimeout.stop()
|
||||||
connectFuture = null
|
connectFutureId = ""
|
||||||
|
|
||||||
let text = qsTr("Unexpected error: %1 [%2]").arg(type).arg(args)
|
let text = qsTr("Unexpected error: %1 [%2]").arg(type).arg(args)
|
||||||
|
|
||||||
|
@ -194,7 +194,7 @@ HBox {
|
||||||
enabled: field.cleanText && ! field.error
|
enabled: field.cleanText && ! field.error
|
||||||
icon.name: "server-connect-to-address"
|
icon.name: "server-connect-to-address"
|
||||||
icon.color: theme.colors.positiveBackground
|
icon.color: theme.colors.positiveBackground
|
||||||
loading: box.connectFuture !== null
|
loading: box.connectFutureId !== ""
|
||||||
disableWhileLoading: false
|
disableWhileLoading: false
|
||||||
onClicked: box.connect()
|
onClicked: box.connect()
|
||||||
|
|
||||||
|
@ -209,7 +209,7 @@ HBox {
|
||||||
onAccepted: window.saveState(this)
|
onAccepted: window.saveState(this)
|
||||||
|
|
||||||
Component.onDestruction:
|
Component.onDestruction:
|
||||||
if (fetchServersFuture) fetchServersFuture.cancel()
|
if (fetchServersFutureId) py.cancelCoro(fetchServersFutureId)
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
id: connectTimeout
|
id: connectTimeout
|
||||||
|
@ -230,7 +230,7 @@ HBox {
|
||||||
Timer {
|
Timer {
|
||||||
interval: 1000
|
interval: 1000
|
||||||
running:
|
running:
|
||||||
fetchServersFuture === null &&
|
fetchServersFutureId === "" &&
|
||||||
ModelStore.get("homeservers").count === 0
|
ModelStore.get("homeservers").count === 0
|
||||||
|
|
||||||
repeat: true
|
repeat: true
|
||||||
|
@ -292,7 +292,7 @@ HBox {
|
||||||
height: width
|
height: width
|
||||||
|
|
||||||
source: "../../Base/HBusyIndicator.qml"
|
source: "../../Base/HBusyIndicator.qml"
|
||||||
active: box.fetchServersFuture && ! serverList.count
|
active: box.fetchServersFutureId && ! serverList.count
|
||||||
opacity: active ? 1 : 0
|
opacity: active ? 1 : 0
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation { factor: 2 } }
|
Behavior on opacity { HNumberAnimation { factor: 2 } }
|
||||||
|
|
|
@ -14,7 +14,7 @@ HFlickableColumnPage {
|
||||||
property string serverUrl
|
property string serverUrl
|
||||||
property string displayUrl: serverUrl
|
property string displayUrl: serverUrl
|
||||||
|
|
||||||
property var loginFuture: null
|
property string loginFutureId: null
|
||||||
|
|
||||||
readonly property int security:
|
readonly property int security:
|
||||||
serverUrl.startsWith("https://") ?
|
serverUrl.startsWith("https://") ?
|
||||||
|
@ -36,7 +36,7 @@ HFlickableColumnPage {
|
||||||
|
|
||||||
function finishSignIn(receivedUserId) {
|
function finishSignIn(receivedUserId) {
|
||||||
errorMessage.text = ""
|
errorMessage.text = ""
|
||||||
page.loginFuture = null
|
page.loginFutureId = ""
|
||||||
|
|
||||||
py.callCoro(
|
py.callCoro(
|
||||||
rememberAccount.checked ?
|
rememberAccount.checked ?
|
||||||
|
@ -53,13 +53,13 @@ HFlickableColumnPage {
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
if (! page.loginFuture) {
|
if (! page.loginFutureId) {
|
||||||
page.exitRequested()
|
page.exitRequested()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
page.loginFuture.cancel()
|
py.cancelCoro(page.loginFutureId)
|
||||||
page.loginFuture = null
|
page.loginFutureId = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -72,7 +72,7 @@ HFlickableColumnPage {
|
||||||
|
|
||||||
text: qsTr("Sign in")
|
text: qsTr("Sign in")
|
||||||
icon.name: "sign-in"
|
icon.name: "sign-in"
|
||||||
loading: page.loginFuture !== null
|
loading: page.loginFutureId !== ""
|
||||||
disableWhileLoading: false
|
disableWhileLoading: false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ HFlickableColumnPage {
|
||||||
|
|
||||||
onKeyboardAccept: if (applyButton.enabled) applyButton.clicked()
|
onKeyboardAccept: if (applyButton.enabled) applyButton.clicked()
|
||||||
onKeyboardCancel: page.cancel()
|
onKeyboardCancel: page.cancel()
|
||||||
Component.onDestruction: if (loginFuture) loginFuture.cancel()
|
Component.onDestruction: if (loginFutureId) py.cancelCoro(loginFutureId)
|
||||||
|
|
||||||
HButton {
|
HButton {
|
||||||
icon.name: "sign-in-" + (
|
icon.name: "sign-in-" + (
|
||||||
|
|
|
@ -12,17 +12,17 @@ SignInBase {
|
||||||
function takeFocus() { idField.item.forceActiveFocus() }
|
function takeFocus() { idField.item.forceActiveFocus() }
|
||||||
|
|
||||||
function signIn() {
|
function signIn() {
|
||||||
if (page.loginFuture) page.loginFuture.cancel()
|
if (page.loginFutureId) page.loginFutureId = ""
|
||||||
|
|
||||||
errorMessage.text = ""
|
errorMessage.text = ""
|
||||||
|
|
||||||
page.loginFuture = py.callCoro(
|
page.loginFutureId = py.callCoro(
|
||||||
"password_auth",
|
"password_auth",
|
||||||
[idField.item.text.trim(), passField.item.text, page.serverUrl],
|
[idField.item.text.trim(), passField.item.text, page.serverUrl],
|
||||||
page.finishSignIn,
|
page.finishSignIn,
|
||||||
|
|
||||||
(type, args, error, traceback, uuid) => {
|
(type, args, error, traceback, uuid) => {
|
||||||
page.loginFuture = null
|
page.loginFutureId = ""
|
||||||
|
|
||||||
let txt = qsTr(
|
let txt = qsTr(
|
||||||
"Invalid request, login type or unknown error: %1",
|
"Invalid request, login type or unknown error: %1",
|
||||||
|
|
|
@ -14,23 +14,23 @@ SignInBase {
|
||||||
function startSignIn() {
|
function startSignIn() {
|
||||||
errorMessage.text = ""
|
errorMessage.text = ""
|
||||||
|
|
||||||
page.loginFuture = py.callCoro("start_sso_auth", [serverUrl], url => {
|
page.loginFutureId = py.callCoro("start_sso_auth",[serverUrl], url => {
|
||||||
urlArea.text = url
|
urlArea.text = url
|
||||||
urlArea.cursorPosition = 0
|
urlArea.cursorPosition = 0
|
||||||
|
|
||||||
Qt.openUrlExternally(url)
|
Qt.openUrlExternally(url)
|
||||||
|
|
||||||
page.loginFuture = py.callCoro("continue_sso_auth", [], userId => {
|
page.loginFutureId = py.callCoro("continue_sso_auth",[],userId => {
|
||||||
page.loginFuture = null
|
page.loginFutureId = ""
|
||||||
page.finishSignIn(userId)
|
page.finishSignIn(userId)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
if (loginFuture) {
|
if (loginFutureId) {
|
||||||
page.loginFuture.cancel()
|
py.cancelCoro(page.loginFutureId)
|
||||||
page.loginFuture = null
|
page.loginFutureId = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
page.exitRequested()
|
page.exitRequested()
|
||||||
|
|
|
@ -13,7 +13,7 @@ import "Timeline"
|
||||||
HColumnPage {
|
HColumnPage {
|
||||||
id: chatPage
|
id: chatPage
|
||||||
|
|
||||||
property var loadMembersFuture: null
|
property string loadMembersFutureId: ""
|
||||||
|
|
||||||
readonly property alias roomHeader: roomHeader
|
readonly property alias roomHeader: roomHeader
|
||||||
readonly property alias eventList: eventList
|
readonly property alias eventList: eventList
|
||||||
|
@ -29,16 +29,17 @@ HColumnPage {
|
||||||
padding: 0
|
padding: 0
|
||||||
column.spacing: 0
|
column.spacing: 0
|
||||||
|
|
||||||
Component.onDestruction: if (loadMembersFuture) loadMembersFuture.cancel()
|
Component.onDestruction:
|
||||||
|
if (loadMembersFutureId) py.cancelCoro(loadMembersFutureId)
|
||||||
|
|
||||||
Timer {
|
Timer {
|
||||||
interval: 200
|
interval: 200
|
||||||
running: ! chat.roomInfo.inviter_id && ! chat.roomInfo.left
|
running: ! chat.roomInfo.inviter_id && ! chat.roomInfo.left
|
||||||
onTriggered: loadMembersFuture = py.callClientCoro(
|
onTriggered: loadMembersFutureId = py.callClientCoro(
|
||||||
chat.userId,
|
chat.userId,
|
||||||
"load_all_room_members",
|
"load_all_room_members",
|
||||||
[chat.roomId],
|
[chat.roomId],
|
||||||
() => { loadMembersFuture = null },
|
() => { loadMembersFutureId = "" },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,13 +7,12 @@ import "../../../.."
|
||||||
import "../../../../Base"
|
import "../../../../Base"
|
||||||
import "../../../../Base/HTile"
|
import "../../../../Base/HTile"
|
||||||
import "../../../../Popups"
|
import "../../../../Popups"
|
||||||
import "../../../../PythonBridge"
|
|
||||||
|
|
||||||
HTile {
|
HTile {
|
||||||
id: member
|
id: member
|
||||||
|
|
||||||
property bool colorName: hovered
|
property bool colorName: hovered
|
||||||
property Future getPresenceFuture: null
|
property string getPresenceFutureId: ""
|
||||||
|
|
||||||
backgroundColor: theme.chat.roomPane.listView.member.background
|
backgroundColor: theme.chat.roomPane.listView.member.background
|
||||||
contentOpacity:
|
contentOpacity:
|
||||||
|
@ -155,11 +154,15 @@ HTile {
|
||||||
|
|
||||||
Component.onCompleted:
|
Component.onCompleted:
|
||||||
if (model.presence === "offline" && model.last_active_at < new Date(1))
|
if (model.presence === "offline" && model.last_active_at < new Date(1))
|
||||||
getPresenceFuture = py.callClientCoro(
|
getPresenceFutureId = py.callClientCoro(
|
||||||
chat.userId, "get_offline_presence", [model.id],
|
chat.userId,
|
||||||
|
"get_offline_presence",
|
||||||
|
[model.id],
|
||||||
|
() => { getPresenceFutureId = "" }
|
||||||
)
|
)
|
||||||
|
|
||||||
Component.onDestruction: if (getPresenceFuture) getPresenceFuture.cancel()
|
Component.onDestruction:
|
||||||
|
if (getPresenceFutureId) py.cancelCoro(getPresenceFutureId)
|
||||||
|
|
||||||
Behavior on contentOpacity { HNumberAnimation {} }
|
Behavior on contentOpacity { HNumberAnimation {} }
|
||||||
Behavior on spacing { HNumberAnimation {} }
|
Behavior on spacing { HNumberAnimation {} }
|
||||||
|
|
|
@ -6,7 +6,6 @@ import QtQuick.Layouts 1.12
|
||||||
import "../../../.."
|
import "../../../.."
|
||||||
import "../../../../Base"
|
import "../../../../Base"
|
||||||
import "../../../../Base/Buttons"
|
import "../../../../Base/Buttons"
|
||||||
import "../../../../PythonBridge"
|
|
||||||
|
|
||||||
HListView {
|
HListView {
|
||||||
id: root
|
id: root
|
||||||
|
@ -21,8 +20,8 @@ HListView {
|
||||||
|
|
||||||
property bool powerLevelFieldFocused: false
|
property bool powerLevelFieldFocused: false
|
||||||
|
|
||||||
property Future setPowerFuture: null
|
property string setPowerFutureId: ""
|
||||||
property Future getPresenceFuture: null
|
property string getPresenceFutureId: ""
|
||||||
|
|
||||||
function loadDevices() {
|
function loadDevices() {
|
||||||
py.callClientCoro(userId, "member_devices", [member.id], devices => {
|
py.callClientCoro(userId, "member_devices", [member.id], devices => {
|
||||||
|
@ -246,14 +245,14 @@ HListView {
|
||||||
ApplyButton {
|
ApplyButton {
|
||||||
id: applyButton
|
id: applyButton
|
||||||
enabled: ! powerLevel.item.fieldOverMaximum
|
enabled: ! powerLevel.item.fieldOverMaximum
|
||||||
loading: setPowerFuture !== null
|
loading: setPowerFutureId !== ""
|
||||||
text: ""
|
text: ""
|
||||||
onClicked: {
|
onClicked: {
|
||||||
setPowerFuture = py.callClientCoro(
|
setPowerFutureId = py.callClientCoro(
|
||||||
userId,
|
userId,
|
||||||
"room_set_member_power",
|
"room_set_member_power",
|
||||||
[roomId, member.id, powerLevel.item.level],
|
[roomId, member.id, powerLevel.item.level],
|
||||||
() => { setPowerFuture = null }
|
() => { setPowerFutureId = "" }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,8 +263,8 @@ HListView {
|
||||||
CancelButton {
|
CancelButton {
|
||||||
text: ""
|
text: ""
|
||||||
onClicked: {
|
onClicked: {
|
||||||
setPowerFuture.cancel()
|
py.cancelCoro(setPowerFutureId)
|
||||||
setPowerFuture = null
|
setPowerFutureId = ""
|
||||||
powerLevel.item.reset()
|
powerLevel.item.reset()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -289,14 +288,18 @@ HListView {
|
||||||
if (member.presence === "offline" &&
|
if (member.presence === "offline" &&
|
||||||
member.last_active_at < new Date(1))
|
member.last_active_at < new Date(1))
|
||||||
{
|
{
|
||||||
getPresenceFuture =
|
getPresenceFutureId = py.callClientCoro(
|
||||||
py.callClientCoro(userId, "get_offline_presence", [member.id])
|
userId,
|
||||||
|
"get_offline_presence",
|
||||||
|
[member.id],
|
||||||
|
() => { getPresenceFutureId = "" }
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
if (setPowerFuture) setPowerFuture.cancel()
|
if (setPowerFutureId) py.cancelCoro(setPowerFutureId)
|
||||||
if (getPresenceFuture) getPresenceFuture.cancel()
|
if (getPresenceFutureId) py.cancelCoro(getPresenceFutureId)
|
||||||
}
|
}
|
||||||
|
|
||||||
Keys.onEnterPressed: Keys.onReturnPressed(event)
|
Keys.onEnterPressed: Keys.onReturnPressed(event)
|
||||||
|
|
|
@ -10,7 +10,7 @@ import "../../../Base/Buttons"
|
||||||
HFlickableColumnPage {
|
HFlickableColumnPage {
|
||||||
id: settingsView
|
id: settingsView
|
||||||
|
|
||||||
property var saveFuture: null
|
property string saveFutureId: ""
|
||||||
|
|
||||||
readonly property bool anyChange:
|
readonly property bool anyChange:
|
||||||
nameField.item.changed || topicArea.item.area.changed ||
|
nameField.item.changed || topicArea.item.area.changed ||
|
||||||
|
@ -20,7 +20,7 @@ HFlickableColumnPage {
|
||||||
readonly property Item keybindFocusItem: nameField.item
|
readonly property Item keybindFocusItem: nameField.item
|
||||||
|
|
||||||
function save() {
|
function save() {
|
||||||
if (saveFuture) saveFuture.cancel()
|
if (saveFutureId) py.cancelCoro(saveFutureId)
|
||||||
|
|
||||||
const args = [
|
const args = [
|
||||||
chat.roomId,
|
chat.roomId,
|
||||||
|
@ -35,17 +35,17 @@ HFlickableColumnPage {
|
||||||
forbidGuestsCheckBox.checked : undefined,
|
forbidGuestsCheckBox.checked : undefined,
|
||||||
]
|
]
|
||||||
|
|
||||||
function onDone() { saveFuture = null }
|
function onDone() { saveFutureId = "" }
|
||||||
|
|
||||||
saveFuture = py.callClientCoro(
|
saveFutureId = py.callClientCoro(
|
||||||
chat.userId, "room_set", args, onDone, onDone,
|
chat.userId, "room_set", args, onDone, onDone,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
function cancel() {
|
function cancel() {
|
||||||
if (saveFuture) {
|
if (saveFutureId) {
|
||||||
saveFuture.cancel()
|
py.cancelCoro(saveFutureId)
|
||||||
saveFuture = null
|
saveFutureId = ""
|
||||||
}
|
}
|
||||||
|
|
||||||
nameField.item.reset()
|
nameField.item.reset()
|
||||||
|
@ -66,14 +66,14 @@ HFlickableColumnPage {
|
||||||
ApplyButton {
|
ApplyButton {
|
||||||
id: applyButton
|
id: applyButton
|
||||||
enabled: anyChange
|
enabled: anyChange
|
||||||
loading: saveFuture !== null
|
loading: saveFutureId !== ""
|
||||||
disableWhileLoading: false
|
disableWhileLoading: false
|
||||||
onClicked: save()
|
onClicked: save()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
CancelButton {
|
CancelButton {
|
||||||
enabled: anyChange || saveFuture !== null
|
enabled: anyChange || saveFutureId !== ""
|
||||||
onClicked: cancel()
|
onClicked: cancel()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,12 +6,11 @@ import QtQuick.Layouts 1.12
|
||||||
import Clipboard 0.1
|
import Clipboard 0.1
|
||||||
import "../../.."
|
import "../../.."
|
||||||
import "../../../Base"
|
import "../../../Base"
|
||||||
import "../../../PythonBridge"
|
|
||||||
|
|
||||||
HColumnLayout {
|
HColumnLayout {
|
||||||
id: eventDelegate
|
id: eventDelegate
|
||||||
|
|
||||||
property var fetchProfilesFuture: null
|
property string fetchProfilesFutureId: ""
|
||||||
|
|
||||||
// Remember timeline goes from newest message at index 0 to oldest
|
// Remember timeline goes from newest message at index 0 to oldest
|
||||||
readonly property var previousModel: eventList.model.get(model.index + 1)
|
readonly property var previousModel: eventList.model.get(model.index + 1)
|
||||||
|
@ -72,16 +71,16 @@ HColumnLayout {
|
||||||
onCursorShapeChanged: eventList.cursorShape = cursorShape
|
onCursorShapeChanged: eventList.cursorShape = cursorShape
|
||||||
|
|
||||||
Component.onCompleted: if (model.fetch_profile)
|
Component.onCompleted: if (model.fetch_profile)
|
||||||
fetchProfilesFuture = py.callClientCoro(
|
fetchProfilesFutureId = py.callClientCoro(
|
||||||
chat.userId,
|
chat.userId,
|
||||||
"get_event_profiles",
|
"get_event_profiles",
|
||||||
[chat.roomId, model.id],
|
[chat.roomId, model.id],
|
||||||
// The if avoids segfault if eventDelegate is already destroyed
|
// The if avoids segfault if eventDelegate is already destroyed
|
||||||
() => { if (eventDelegate) fetchProfilesFuture = null }
|
() => { if (eventDelegate) fetchProfilesFutureId = "" }
|
||||||
)
|
)
|
||||||
|
|
||||||
Component.onDestruction:
|
Component.onDestruction:
|
||||||
if (fetchProfilesFuture) fetchProfilesFuture.cancel()
|
if (fetchProfilesFutureId) py.cancelCoro(fetchProfilesFutureId)
|
||||||
|
|
||||||
ListView.onRemove: eventList.uncheck(model.id)
|
ListView.onRemove: eventList.uncheck(model.id)
|
||||||
|
|
||||||
|
|
|
@ -218,8 +218,8 @@ Rectangle {
|
||||||
HListView {
|
HListView {
|
||||||
id: eventList
|
id: eventList
|
||||||
|
|
||||||
property Future updateMarkerFuture: null
|
property string updateMarkerFutureId: ""
|
||||||
property Future loadPastEventsFuture: null
|
property string loadPastEventsFutureId: ""
|
||||||
property bool moreToLoad: true
|
property bool moreToLoad: true
|
||||||
|
|
||||||
property bool ownEventsOnLeft:
|
property bool ownEventsOnLeft:
|
||||||
|
@ -355,13 +355,13 @@ Rectangle {
|
||||||
}
|
}
|
||||||
|
|
||||||
function loadPastEvents() {
|
function loadPastEvents() {
|
||||||
loadPastEventsFuture = py.callClientCoro(
|
loadPastEventsFutureId = py.callClientCoro(
|
||||||
chat.userId,
|
chat.userId,
|
||||||
"load_past_events",
|
"load_past_events",
|
||||||
[chat.roomId],
|
[chat.roomId],
|
||||||
more => {
|
more => {
|
||||||
moreToLoad = more
|
moreToLoad = more
|
||||||
loadPastEventsFuture = null
|
loadPastEventsFutureId = ""
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -519,7 +519,7 @@ Rectangle {
|
||||||
footer: Item {
|
footer: Item {
|
||||||
width: eventList.width
|
width: eventList.width
|
||||||
height: (button.height + theme.spacing * 2) * opacity
|
height: (button.height + theme.spacing * 2) * opacity
|
||||||
opacity: eventList.loadPastEventsFuture ? 1 : 0
|
opacity: eventList.loadPastEventsFutureId ? 1 : 0
|
||||||
visible: opacity > 0
|
visible: opacity > 0
|
||||||
|
|
||||||
Behavior on opacity { HNumberAnimation {} }
|
Behavior on opacity { HNumberAnimation {} }
|
||||||
|
@ -554,14 +554,14 @@ Rectangle {
|
||||||
interval: 200
|
interval: 200
|
||||||
running:
|
running:
|
||||||
eventList.shouldLoadPastEvents &&
|
eventList.shouldLoadPastEvents &&
|
||||||
! eventList.loadPastEventsFuture
|
! eventList.loadPastEventsFutureId
|
||||||
triggeredOnStart: true
|
triggeredOnStart: true
|
||||||
onTriggered: eventList.loadPastEvents()
|
onTriggered: eventList.loadPastEvents()
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Component.onDestruction: {
|
Component.onDestruction: {
|
||||||
if (loadPastEventsFuture) loadPastEventsFuture.cancel()
|
if (loadPastEventsFutureId) py.cancelCoro(loadPastEventsFutureId)
|
||||||
}
|
}
|
||||||
|
|
||||||
MouseArea {
|
MouseArea {
|
||||||
|
@ -585,7 +585,7 @@ Rectangle {
|
||||||
interval: Math.max(100, window.settings.markRoomReadMsecDelay)
|
interval: Math.max(100, window.settings.markRoomReadMsecDelay)
|
||||||
|
|
||||||
running:
|
running:
|
||||||
! eventList.updateMarkerFuture &&
|
! eventList.updateMarkerFutureId &&
|
||||||
(
|
(
|
||||||
chat.roomInfo.unreads ||
|
chat.roomInfo.unreads ||
|
||||||
chat.roomInfo.highlights ||
|
chat.roomInfo.highlights ||
|
||||||
|
@ -600,11 +600,11 @@ Rectangle {
|
||||||
const item = eventList.model.get(i)
|
const item = eventList.model.get(i)
|
||||||
|
|
||||||
if (item.sender !== chat.userId) {
|
if (item.sender !== chat.userId) {
|
||||||
eventList.updateMarkerFuture = py.callCoro(
|
eventList.updateMarkerFutureId = py.callCoro(
|
||||||
"update_room_read_marker",
|
"update_room_read_marker",
|
||||||
[chat.roomId, item.event_id],
|
[chat.roomId, item.event_id],
|
||||||
() => { eventList.updateMarkerFuture = null },
|
() => { eventList.updateMarkerFutureId = "" },
|
||||||
() => { eventList.updateMarkerFuture = null },
|
() => { eventList.updateMarkerFutureId = "" },
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
import QtQuick 2.12
|
import QtQuick 2.12
|
||||||
import "../Base"
|
import "../Base"
|
||||||
import "../Base/Buttons"
|
import "../Base/Buttons"
|
||||||
import "../PythonBridge"
|
|
||||||
|
|
||||||
PasswordPopup {
|
PasswordPopup {
|
||||||
id: popup
|
id: popup
|
||||||
|
@ -13,15 +12,15 @@ PasswordPopup {
|
||||||
property var deviceIds // array
|
property var deviceIds // array
|
||||||
property var deletedCallback: null
|
property var deletedCallback: null
|
||||||
|
|
||||||
property Future deleteFuture: null
|
property string deleteFutureId: ""
|
||||||
|
|
||||||
function verifyPassword(pass, callback) {
|
function verifyPassword(pass, callback) {
|
||||||
deleteFuture = py.callClientCoro(
|
deleteFutureId = py.callClientCoro(
|
||||||
userId,
|
userId,
|
||||||
"delete_devices_with_password",
|
"delete_devices_with_password",
|
||||||
[deviceIds, pass],
|
[deviceIds, pass],
|
||||||
() => {
|
() => {
|
||||||
deleteFuture = null
|
deleteFutureId = ""
|
||||||
callback(true)
|
callback(true)
|
||||||
},
|
},
|
||||||
(type, args) => {
|
(type, args) => {
|
||||||
|
@ -47,9 +46,9 @@ PasswordPopup {
|
||||||
validateButton.icon.name: "sign-out"
|
validateButton.icon.name: "sign-out"
|
||||||
|
|
||||||
onClosed: {
|
onClosed: {
|
||||||
if (deleteFuture) deleteFuture.cancel()
|
if (deleteFutureId) py.cancelCoro(deleteFutureId)
|
||||||
|
|
||||||
if (deleteFuture || acceptedPassword && deletedCallback)
|
if (deleteFutureId || acceptedPassword && deletedCallback)
|
||||||
deletedCallback()
|
deletedCallback()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,7 +15,7 @@ HColumnPopup {
|
||||||
property string roomName
|
property string roomName
|
||||||
property bool invitingAllowed: true
|
property bool invitingAllowed: true
|
||||||
|
|
||||||
property var inviteFuture: null
|
property string inviteFutureId: ""
|
||||||
property var successfulInvites: []
|
property var successfulInvites: []
|
||||||
property var failedInvites: []
|
property var failedInvites: []
|
||||||
|
|
||||||
|
@ -26,12 +26,14 @@ HColumnPopup {
|
||||||
user => ! successfulInvites.includes(user)
|
user => ! successfulInvites.includes(user)
|
||||||
)
|
)
|
||||||
|
|
||||||
inviteFuture = py.callClientCoro(
|
inviteFutureId = py.callClientCoro(
|
||||||
userId,
|
userId,
|
||||||
"room_mass_invite",
|
"room_mass_invite",
|
||||||
[roomId, ...inviteesLeft],
|
[roomId, ...inviteesLeft],
|
||||||
|
|
||||||
([successes, errors]) => {
|
([successes, errors]) => {
|
||||||
|
inviteFutureId = ""
|
||||||
|
|
||||||
if (errors.length < 1) {
|
if (errors.length < 1) {
|
||||||
popup.close()
|
popup.close()
|
||||||
return
|
return
|
||||||
|
@ -61,10 +63,10 @@ HColumnPopup {
|
||||||
}
|
}
|
||||||
|
|
||||||
onOpened: inviteArea.forceActiveFocus()
|
onOpened: inviteArea.forceActiveFocus()
|
||||||
onClosed: if (inviteFuture) inviteFuture.cancel()
|
onClosed: if (inviteFutureId) py.cancelCoro(inviteFutureId)
|
||||||
|
|
||||||
onInvitingAllowedChanged:
|
onInvitingAllowedChanged:
|
||||||
if (! invitingAllowed && inviteFuture) inviteFuture.cancel()
|
if (! invitingAllowed && inviteFutureId) py.cancelCoro(inviteFutureId)
|
||||||
|
|
||||||
SummaryLabel {
|
SummaryLabel {
|
||||||
text: qsTr("Invite members to <i>%1</i>").arg(roomName)
|
text: qsTr("Invite members to <i>%1</i>").arg(roomName)
|
||||||
|
|
|
@ -1,26 +0,0 @@
|
||||||
// Copyright Mirage authors & contributors <https://github.com/mirukana/mirage>
|
|
||||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
|
||||||
|
|
||||||
import QtQuick 2.12
|
|
||||||
|
|
||||||
QtObject {
|
|
||||||
id: future
|
|
||||||
|
|
||||||
property PythonBridge bridge
|
|
||||||
|
|
||||||
readonly property QtObject privates: QtObject {
|
|
||||||
property var pythonFuture: null
|
|
||||||
property bool cancelPending: false
|
|
||||||
|
|
||||||
onPythonFutureChanged: if (cancelPending) future.cancel()
|
|
||||||
}
|
|
||||||
|
|
||||||
function cancel() {
|
|
||||||
if (! privates.pythonFuture) {
|
|
||||||
privates.cancelPending = true
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
bridge.call(bridge.getattr(privates.pythonFuture, "cancel"))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -11,48 +11,39 @@ Python {
|
||||||
|
|
||||||
readonly property var pendingCoroutines: Globals.pendingCoroutines
|
readonly property var pendingCoroutines: Globals.pendingCoroutines
|
||||||
|
|
||||||
function makeFuture(callback) {
|
|
||||||
return Qt.createComponent("Future.qml").createObject(py, {bridge: py})
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
|
|
||||||
function callCoro(name, args=[], onSuccess=null, onError=null) {
|
function callCoro(name, args=[], onSuccess=null, onError=null) {
|
||||||
const uuid = name + "." + CppUtils.uuid()
|
const uuid = name + "." + CppUtils.uuid()
|
||||||
const future = makeFuture()
|
|
||||||
|
|
||||||
Globals.pendingCoroutines[uuid] = {future, onSuccess, onError}
|
Globals.pendingCoroutines[uuid] = {onSuccess, onError}
|
||||||
Globals.pendingCoroutinesChanged()
|
Globals.pendingCoroutinesChanged()
|
||||||
// if (name === "models.ensure_exists_from_qml") { print("r"); return}
|
|
||||||
|
|
||||||
call("BRIDGE.call_backend_coro", [name, uuid, args], pyFuture => {
|
call("BRIDGE.call_backend_coro", [name, uuid, args])
|
||||||
future.privates.pythonFuture = pyFuture
|
return uuid
|
||||||
})
|
|
||||||
|
|
||||||
return future
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function callClientCoro(
|
function callClientCoro(
|
||||||
accountId, name, args=[], onSuccess=null, onError=null
|
accountId, name, args=[], onSuccess=null, onError=null
|
||||||
) {
|
) {
|
||||||
const future = makeFuture()
|
|
||||||
|
|
||||||
callCoro("get_client", [accountId, [name, args]], () => {
|
|
||||||
const uuid = accountId + "." + name + "." + CppUtils.uuid()
|
const uuid = accountId + "." + name + "." + CppUtils.uuid()
|
||||||
|
|
||||||
Globals.pendingCoroutines[uuid] = {onSuccess, onError}
|
Globals.pendingCoroutines[uuid] = {onSuccess, onError}
|
||||||
Globals.pendingCoroutinesChanged()
|
Globals.pendingCoroutinesChanged()
|
||||||
|
|
||||||
const call_args = [accountId, name, uuid, args]
|
// Ensure the client exists or wait for it to exist
|
||||||
|
callCoro("get_client", [accountId, [name, args]], () => {
|
||||||
call("BRIDGE.call_client_coro", call_args, pyFuture => {
|
// Now that we're sure it won't error, run that client's function
|
||||||
future.privates.pythonFuture = pyFuture
|
call("BRIDGE.call_client_coro", [accountId, name, uuid, args])
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return future
|
return uuid
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelCoro(uuid) {
|
||||||
|
call("BRIDGE.cancel_coro", [uuid])
|
||||||
}
|
}
|
||||||
|
|
||||||
function saveConfig(backend_attribute, data, callback=null) {
|
function saveConfig(backend_attribute, data, callback=null) {
|
||||||
|
|
Loading…
Reference in New Issue
Block a user