diff --git a/src/python/matrix_client.py b/src/python/matrix_client.py index ced451d4..a4bc6bb5 100644 --- a/src/python/matrix_client.py +++ b/src/python/matrix_client.py @@ -7,6 +7,7 @@ import platform from contextlib import suppress from datetime import datetime from enum import Enum +from functools import partial from pathlib import Path from types import ModuleType from typing import DefaultDict, Dict, Optional, Set, Tuple, Type, Union @@ -303,6 +304,32 @@ class MatrixClient(nio.AsyncClient): return True + async def import_keys(self, infile: str, passphrase: str) -> Optional[str]: + # Reimplemented until better solutions are worked on in nio + loop = asyncio.get_event_loop() + + import_keys = partial(self.olm.import_keys_static, infile, passphrase) + try: + sessions = await loop.run_in_executor(None, import_keys) + except nio.EncryptionError as err: + return str(err) + + account = self.models[Account][self.user_id] + account.importing_key = 0 + account.total_keys_to_import = len(sessions) + + for session in sessions: + if self.olm.inbound_group_store.add(session): + await loop.run_in_executor( + None, self.store.save_inbound_group_session, session, + ) + account.importing_key += 1 + + account.importing_key = 0 + account.total_keys_to_import = 0 + return None + + # Functions to register data into models async def event_is_past(self, ev: Union[nio.Event, Event]) -> bool: diff --git a/src/python/models/items.py b/src/python/models/items.py index ffaf3176..27e6be46 100644 --- a/src/python/models/items.py +++ b/src/python/models/items.py @@ -16,6 +16,9 @@ class Account(ModelItem): first_sync_done: bool = False profile_updated: Optional[datetime] = None + importing_key: int = 0 + total_keys_to_import: int = 0 + def __lt__(self, other: "Account") -> bool: name = self.display_name or self.user_id[1:] other_name = other.display_name or other.user_id[1:] diff --git a/src/qml/Pages/EditAccount/Encryption.qml b/src/qml/Pages/EditAccount/Encryption.qml index 7e27cab1..af80436b 100644 --- a/src/qml/Pages/EditAccount/Encryption.qml +++ b/src/qml/Pages/EditAccount/Encryption.qml @@ -1,81 +1,19 @@ import QtQuick 2.12 -import QtQuick.Controls 2.12 -import QtQuick.Layouts 1.12 import "../../Base" -import "../../utils.js" as Utils -HColumnLayout { - function importKeys(file, passphrase) { - importButton.loading = true +HLoader { + id: loader + source: accountInfo.total_keys_to_import ? + "ImportingKeys.qml" : "ImportExportKeys.qml" - let path = file.toString().replace(/^file:\/\//, "") + onSourceChanged: animation.running = true - py.callClientCoro( - editAccount.userId, "import_keys", [path, passphrase], () => { - print("import done") - importButton.loading = false - } - ) - } - - HLabel { - wrapMode: Text.Wrap - text: qsTr( - "The decryption keys for messages you received in encrypted " + - "rooms can be exported to a passphrase-protected file.%1" + - "You will then be able to import this file in another " + - "Matrix client." - ).arg(pageLoader.isWide ? "\n" :"\n\n") - - Layout.fillWidth: true - Layout.margins: currentSpacing - } - - HRowLayout { - HButton { - id: exportButton - icon.name: "export-keys" - text: qsTr("Export") - enabled: false - - Layout.fillWidth: true - Layout.alignment: Qt.AlignBottom - } - - HButton { - id: importButton - icon.name: "import-keys" - text: qsTr("Import") - - Layout.fillWidth: true - Layout.alignment: Qt.AlignBottom - - HFileDialogOpener { - id: fileDialog - dialog.title: qsTr("Select a decryption key file to import") - onFileChanged: { - importPasswordPopup.file = file - importPasswordPopup.open() - } - } - } - } - - HPasswordPopup { - property url file: "" - - function verifyPassword(pass, callback) { - return py.callCoro( - "check_exported_keys_passphrase", - [file.toString().replace(/^file:\/\//, ""), pass], - callback - ) - } - - id: importPasswordPopup - label.text: qsTr( - "Please enter the passphrase that was used to protect this file:" - ) - onAcceptedPasswordChanged: importKeys(file, acceptedPassword) + HNumberAnimation { + id: animation + target: loader.item + property: "scale" + from: 0 + to: 1 + overshoot: 3 } } diff --git a/src/qml/Pages/EditAccount/ImportExportKeys.qml b/src/qml/Pages/EditAccount/ImportExportKeys.qml new file mode 100644 index 00000000..055ebe74 --- /dev/null +++ b/src/qml/Pages/EditAccount/ImportExportKeys.qml @@ -0,0 +1,80 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import "../../Base" +import "../../utils.js" as Utils + +HColumnLayout { + function importKeys(file, passphrase) { + importButton.loading = true + + let path = file.toString().replace(/^file:\/\//, "") + + py.callClientCoro( + editAccount.userId, "import_keys", [path, passphrase], () => { + if (importButton) importButton.loading = false + } + ) + } + + HLabel { + wrapMode: Text.Wrap + text: qsTr( + "The decryption keys for messages you received in encrypted " + + "rooms can be exported to a passphrase-protected file.%1" + + "You will then be able to import this file in another " + + "Matrix client." + ).arg(pageLoader.isWide ? "\n" :"\n\n") + + Layout.fillWidth: true + Layout.margins: currentSpacing + } + + HRowLayout { + HButton { + id: exportButton + icon.name: "export-keys" + text: qsTr("Export") + enabled: false + + Layout.fillWidth: true + Layout.alignment: Qt.AlignBottom + } + + HButton { + id: importButton + icon.name: "import-keys" + text: qsTr("Import") + + Layout.fillWidth: true + Layout.alignment: Qt.AlignBottom + + HFileDialogOpener { + id: fileDialog + dialog.title: qsTr("Select a decryption key file to import") + onFileChanged: { + importPasswordPopup.file = file + importPasswordPopup.open() + } + } + } + } + + HPasswordPopup { + property url file: "" + + function verifyPassword(pass, callback) { + return py.callCoro( + "check_exported_keys_passphrase", + [file.toString().replace(/^file:\/\//, ""), pass], + callback + ) + } + + id: importPasswordPopup + label.text: qsTr( + "Please enter the passphrase that was used to protect this file:" + ) + onAcceptedPasswordChanged: importKeys(file, acceptedPassword) + } +} diff --git a/src/qml/Pages/EditAccount/ImportingKeys.qml b/src/qml/Pages/EditAccount/ImportingKeys.qml new file mode 100644 index 00000000..4eac8caf --- /dev/null +++ b/src/qml/Pages/EditAccount/ImportingKeys.qml @@ -0,0 +1,36 @@ +import QtQuick 2.12 +import QtQuick.Controls 2.12 +import QtQuick.Layouts 1.12 +import "../../Base" +import "../../utils.js" as Utils + +HColumnLayout { + HRowLayout { + HLabel { + text: qsTr("Importing decryption keys...") + elide: Text.ElideRight + + Layout.fillWidth: true + Layout.margins: currentSpacing + } + + HLabel { + text: qsTr("%1/%2") + .arg(Math.ceil(progressBar.value)).arg(progressBar.to) + + Layout.margins: currentSpacing + Layout.leftMargin: 0 + } + } + + ProgressBar { + id: progressBar + from: 0 + value: accountInfo.importing_key + to: accountInfo.total_keys_to_import + + Behavior on value { HNumberAnimation { factor: 5 } } + + Layout.fillWidth: true + } +}