diff --git a/TODO.md b/TODO.md index 32016d36..8ec3daca 100644 --- a/TODO.md +++ b/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 `\n` messages - filter > enter > room list is always scrolled to top diff --git a/src/backend/qml_bridge.py b/src/backend/qml_bridge.py index d534a06f..848bf728 100644 --- a/src/backend/qml_bridge.py +++ b/src/backend/qml_bridge.py @@ -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: diff --git a/src/gui/Base/HMxcImage.qml b/src/gui/Base/HMxcImage.qml index 4ddf3f86..073d0901 100644 --- a/src/gui/Base/HMxcImage.qml +++ b/src/gui/Base/HMxcImage.qml @@ -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) } diff --git a/src/gui/Dialogs/ImportKeys.qml b/src/gui/Dialogs/ImportKeys.qml index 5a086a31..95cbfb0e 100644 --- a/src/gui/Dialogs/ImportKeys.qml +++ b/src/gui/Dialogs/ImportKeys.qml @@ -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 } } } diff --git a/src/gui/MainPane/RoomDelegate.qml b/src/gui/MainPane/RoomDelegate.qml index e1b46314..ee9245d7 100644 --- a/src/gui/MainPane/RoomDelegate.qml +++ b/src/gui/MainPane/RoomDelegate.qml @@ -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 = "" }, ) } } diff --git a/src/gui/Pages/AccountSettings/Sessions.qml b/src/gui/Pages/AccountSettings/Sessions.qml index 858b7ee1..7b32b792 100644 --- a/src/gui/Pages/AccountSettings/Sessions.qml +++ b/src/gui/Pages/AccountSettings/Sessions.qml @@ -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 } } diff --git a/src/gui/Pages/AddAccount/ServerBrowser.qml b/src/gui/Pages/AddAccount/ServerBrowser.qml index f2b3854a..bda55e2a 100644 --- a/src/gui/Pages/AddAccount/ServerBrowser.qml +++ b/src/gui/Pages/AddAccount/ServerBrowser.qml @@ -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 } } diff --git a/src/gui/Pages/AddAccount/SignInBase.qml b/src/gui/Pages/AddAccount/SignInBase.qml index 5f47ac8f..f8545d8a 100644 --- a/src/gui/Pages/AddAccount/SignInBase.qml +++ b/src/gui/Pages/AddAccount/SignInBase.qml @@ -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-" + ( diff --git a/src/gui/Pages/AddAccount/SignInPassword.qml b/src/gui/Pages/AddAccount/SignInPassword.qml index fbe3cc4c..00d89272 100644 --- a/src/gui/Pages/AddAccount/SignInPassword.qml +++ b/src/gui/Pages/AddAccount/SignInPassword.qml @@ -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", diff --git a/src/gui/Pages/AddAccount/SignInSso.qml b/src/gui/Pages/AddAccount/SignInSso.qml index 8b0c1e94..4606f69c 100644 --- a/src/gui/Pages/AddAccount/SignInSso.qml +++ b/src/gui/Pages/AddAccount/SignInSso.qml @@ -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() diff --git a/src/gui/Pages/Chat/ChatPage.qml b/src/gui/Pages/Chat/ChatPage.qml index f88abbce..cb1f77d8 100644 --- a/src/gui/Pages/Chat/ChatPage.qml +++ b/src/gui/Pages/Chat/ChatPage.qml @@ -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 = "" }, ) } diff --git a/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml b/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml index bb9b48c6..3ed00193 100644 --- a/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml +++ b/src/gui/Pages/Chat/RoomPane/MemberView/MemberDelegate.qml @@ -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 {} } diff --git a/src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml b/src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml index 6e0a37ad..0391ee2a 100644 --- a/src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml +++ b/src/gui/Pages/Chat/RoomPane/MemberView/MemberProfile.qml @@ -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) diff --git a/src/gui/Pages/Chat/RoomPane/SettingsView.qml b/src/gui/Pages/Chat/RoomPane/SettingsView.qml index 1e064c6e..93e9b137 100644 --- a/src/gui/Pages/Chat/RoomPane/SettingsView.qml +++ b/src/gui/Pages/Chat/RoomPane/SettingsView.qml @@ -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() } } diff --git a/src/gui/Pages/Chat/Timeline/EventDelegate.qml b/src/gui/Pages/Chat/Timeline/EventDelegate.qml index c91c19f4..a5244eb9 100644 --- a/src/gui/Pages/Chat/Timeline/EventDelegate.qml +++ b/src/gui/Pages/Chat/Timeline/EventDelegate.qml @@ -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) diff --git a/src/gui/Pages/Chat/Timeline/EventList.qml b/src/gui/Pages/Chat/Timeline/EventList.qml index 170f9b91..4e7cfc1f 100644 --- a/src/gui/Pages/Chat/Timeline/EventList.qml +++ b/src/gui/Pages/Chat/Timeline/EventList.qml @@ -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 } diff --git a/src/gui/Popups/DeleteDevicesPopup.qml b/src/gui/Popups/DeleteDevicesPopup.qml index eacf8b86..01bf2670 100644 --- a/src/gui/Popups/DeleteDevicesPopup.qml +++ b/src/gui/Popups/DeleteDevicesPopup.qml @@ -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() } } diff --git a/src/gui/Popups/InviteToRoomPopup.qml b/src/gui/Popups/InviteToRoomPopup.qml index c81d6917..450ac63a 100644 --- a/src/gui/Popups/InviteToRoomPopup.qml +++ b/src/gui/Popups/InviteToRoomPopup.qml @@ -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 %1").arg(roomName) diff --git a/src/gui/PythonBridge/Future.qml b/src/gui/PythonBridge/Future.qml deleted file mode 100644 index 0cc8d797..00000000 --- a/src/gui/PythonBridge/Future.qml +++ /dev/null @@ -1,26 +0,0 @@ -// Copyright Mirage authors & contributors -// 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")) - } -} diff --git a/src/gui/PythonBridge/PythonBridge.qml b/src/gui/PythonBridge/PythonBridge.qml index 426dc596..029fe8a3 100644 --- a/src/gui/PythonBridge/PythonBridge.qml +++ b/src/gui/PythonBridge/PythonBridge.qml @@ -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) {