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