Refactor SecureBackupControllerListener into SDKListener and use it everywhere. (#4030)

This commit is contained in:
Doug
2025-04-16 08:36:57 +01:00
committed by GitHub
parent c5bb960234
commit 8c9773e4de
11 changed files with 125 additions and 171 deletions
@@ -2002,8 +2002,8 @@ class AuthenticationClientBuilderMock: AuthenticationClientBuilderProtocol, @unc
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerCalled: Bool {
return buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerCallsCount > 0
}
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReceivedArguments: (qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: QrLoginProgressListenerProxy)?
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReceivedInvocations: [(qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: QrLoginProgressListenerProxy)] = []
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReceivedArguments: (qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: SDKListener<QrLoginProgress>)?
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReceivedInvocations: [(qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: SDKListener<QrLoginProgress>)] = []
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerUnderlyingReturnValue: ClientProtocol!
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerReturnValue: ClientProtocol! {
@@ -2029,9 +2029,9 @@ class AuthenticationClientBuilderMock: AuthenticationClientBuilderProtocol, @unc
}
}
}
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerClosure: ((QrCodeData, OIDCConfigurationProxy, QrLoginProgressListenerProxy) async throws -> ClientProtocol)?
var buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerClosure: ((QrCodeData, OIDCConfigurationProxy, SDKListener<QrLoginProgress>) async throws -> ClientProtocol)?
func buildWithQRCode(qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: QrLoginProgressListenerProxy) async throws -> ClientProtocol {
func buildWithQRCode(qrCodeData: QrCodeData, oidcConfiguration: OIDCConfigurationProxy, progressListener: SDKListener<QrLoginProgress>) async throws -> ClientProtocol {
if let error = buildWithQRCodeQrCodeDataOidcConfigurationProgressListenerThrowableError {
throw error
}
+101
View File
@@ -0,0 +1,101 @@
//
// Copyright 2025 New Vector Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
// Please see LICENSE files in the repository root for full details.
//
import Foundation
import MatrixRustSDK
/// A helper class that can be passed as this listener for SDK callbacks.
///
/// To use this you'll need to add a conformance to the required listener
/// protocol with a specialisation for the type it listens for.
final class SDKListener<T> {
private let onUpdateClosure: (T) -> Void
/// Creates a new listener.
/// - Parameter onUpdateClosure: A closure that will be called whenever a new value is available.
init(_ onUpdateClosure: @escaping (T) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
}
// MARK: QRCodeLoginService
extension SDKListener: QrLoginProgressListener where T == QrLoginProgress {
func onUpdate(state: QrLoginProgress) { onUpdateClosure(state) }
}
// MARK: ClientProxy
extension SDKListener: SyncServiceStateObserver where T == SyncServiceState {
func onUpdate(state: SyncServiceState) { onUpdateClosure(state) }
}
extension SDKListener: RoomListServiceStateListener where T == RoomListServiceState {
func onUpdate(state: RoomListServiceState) { onUpdateClosure(state) }
}
extension SDKListener: RoomListServiceSyncIndicatorListener where T == RoomListServiceSyncIndicator {
func onUpdate(syncIndicator: RoomListServiceSyncIndicator) { onUpdateClosure(syncIndicator) }
}
extension SDKListener: VerificationStateListener where T == VerificationState {
func onUpdate(status: VerificationState) { onUpdateClosure(status) }
}
// MARK: SecureBackupController
extension SDKListener: BackupStateListener where T == BackupState {
func onUpdate(status: BackupState) { onUpdateClosure(status) }
}
extension SDKListener: RecoveryStateListener where T == RecoveryState {
func onUpdate(status: RecoveryState) { onUpdateClosure(status) }
}
extension SDKListener: EnableRecoveryProgressListener where T == EnableRecoveryProgress {
func onUpdate(status: EnableRecoveryProgress) { onUpdateClosure(status) }
}
extension SDKListener: BackupSteadyStateListener where T == BackupUploadState {
func onUpdate(status: BackupUploadState) { onUpdateClosure(status) }
}
// MARK: RoomSummaryProvider
extension SDKListener: RoomListEntriesListener where T == [RoomListEntriesUpdate] {
func onUpdate(roomEntriesUpdate: [RoomListEntriesUpdate]) { onUpdateClosure(roomEntriesUpdate) }
}
extension SDKListener: RoomListLoadingStateListener where T == RoomListLoadingState {
func onUpdate(state: RoomListLoadingState) { onUpdateClosure(state) }
}
// MARK: TimelineProxy
extension SDKListener: PaginationStatusListener where T == RoomPaginationStatus {
func onUpdate(status: RoomPaginationStatus) { onUpdateClosure(status) }
}
extension SDKListener: ProgressWatcher where T == Double {
func transmissionProgress(progress: TransmissionProgress) {
DispatchQueue.main.async { [weak self] in
self?.onUpdateClosure(Double(progress.current) / Double(progress.total))
}
}
}
// MARK: TimelineProvider
extension SDKListener: TimelineListener where T == [TimelineDiff] {
func onUpdate(diff: [TimelineDiff]) { onUpdateClosure(diff) }
}
// MARK: RoomDirectorySearchProxy
extension SDKListener: RoomDirectorySearchEntriesListener where T == [RoomDirectorySearchEntryUpdate] {
func onUpdate(roomEntriesUpdate: [RoomDirectorySearchEntryUpdate]) { onUpdateClosure(roomEntriesUpdate) }
}
@@ -13,7 +13,7 @@ protocol AuthenticationClientBuilderProtocol {
func build(homeserverAddress: String) async throws -> ClientProtocol
func buildWithQRCode(qrCodeData: QrCodeData,
oidcConfiguration: OIDCConfigurationProxy,
progressListener: QrLoginProgressListenerProxy) async throws -> ClientProtocol
progressListener: SDKListener<QrLoginProgress>) async throws -> ClientProtocol
}
/// A wrapper around `ClientBuilder` to share reusable code between Normal and QR logins.
@@ -33,7 +33,7 @@ struct AuthenticationClientBuilder: AuthenticationClientBuilderProtocol {
/// Builds a Client, authenticating with the given QR code data.
func buildWithQRCode(qrCodeData: QrCodeData,
oidcConfiguration: OIDCConfigurationProxy,
progressListener: QrLoginProgressListenerProxy) async throws -> ClientProtocol {
progressListener: SDKListener<QrLoginProgress>) async throws -> ClientProtocol {
try await makeClientBuilder().buildWithQrCode(qrCodeData: qrCodeData,
oidcConfiguration: oidcConfiguration.rustValue,
progressListener: progressListener)
@@ -192,7 +192,7 @@ class ClientProxy: ClientProxyProtocol {
await updateVerificationState(client.encryption().verificationState())
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: VerificationStateListenerProxy { [weak self] verificationState in
verificationStateListenerTaskHandle = client.encryption().verificationStateListener(listener: SDKListener { [weak self] verificationState in
Task { await self?.updateVerificationState(verificationState) }
})
@@ -838,7 +838,7 @@ class ClientProxy: ClientProxyProtocol {
}
private func createSyncServiceStateObserver(_ syncService: SyncService) -> TaskHandle {
syncService.state(listener: SyncServiceStateObserverProxy { [weak self] state in
syncService.state(listener: SDKListener { [weak self] state in
guard let self else { return }
MXLog.info("Received sync service update: \(state)")
@@ -856,7 +856,7 @@ class ClientProxy: ClientProxyProtocol {
}
private func createRoomListServiceObserver(_ roomListService: RoomListService) -> TaskHandle {
roomListService.state(listener: RoomListStateListenerProxy { [weak self] state in
roomListService.state(listener: SDKListener { [weak self] state in
guard let self else { return }
MXLog.info("Received room list update: \(state)")
@@ -877,7 +877,7 @@ class ClientProxy: ClientProxyProtocol {
}
private func createRoomListLoadingStateUpdateObserver(_ roomListService: RoomListService) -> TaskHandle {
roomListService.syncIndicator(delayBeforeShowingInMs: 1000, delayBeforeHidingInMs: 0, listener: RoomListServiceSyncIndicatorListenerProxy { [weak self] state in
roomListService.syncIndicator(delayBeforeShowingInMs: 1000, delayBeforeHidingInMs: 0, listener: SDKListener { [weak self] state in
guard let self else { return }
switch state {
@@ -1039,54 +1039,6 @@ extension ClientProxy: MediaLoaderProtocol {
}
}
private class SyncServiceStateObserverProxy: SyncServiceStateObserver {
private let onUpdateClosure: (SyncServiceState) -> Void
init(onUpdateClosure: @escaping (SyncServiceState) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(state: SyncServiceState) {
onUpdateClosure(state)
}
}
private class RoomListStateListenerProxy: RoomListServiceStateListener {
private let onUpdateClosure: (RoomListServiceState) -> Void
init(onUpdateClosure: @escaping (RoomListServiceState) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(state: RoomListServiceState) {
onUpdateClosure(state)
}
}
private class RoomListServiceSyncIndicatorListenerProxy: RoomListServiceSyncIndicatorListener {
private let onUpdateClosure: (RoomListServiceSyncIndicator) -> Void
init(onUpdateClosure: @escaping (RoomListServiceSyncIndicator) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(syncIndicator: RoomListServiceSyncIndicator) {
onUpdateClosure(syncIndicator)
}
}
private class VerificationStateListenerProxy: VerificationStateListener {
private let onUpdateClosure: (VerificationState) -> Void
init(onUpdateClosure: @escaping (VerificationState) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(status: VerificationState) {
onUpdateClosure(status)
}
}
private class ClientDelegateWrapper: ClientDelegate {
private let authErrorCallback: (Bool) -> Void
@@ -43,7 +43,7 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
return .failure(.invalidQRCode)
}
let listener = QrLoginProgressListenerProxy { [weak self] progress in
let listener = SDKListener { [weak self] progress in
self?.qrLoginProgressSubject.send(progress)
}
@@ -92,18 +92,6 @@ final class QRCodeLoginService: QRCodeLoginServiceProtocol {
}
}
final class QrLoginProgressListenerProxy: QrLoginProgressListener {
private let onUpdateClosure: (QrLoginProgress) -> Void
init(onUpdateClosure: @escaping (QrLoginProgress) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(state: QrLoginProgress) {
onUpdateClosure(state)
}
}
private extension HumanQrLoginError {
var serviceError: QRCodeLoginServiceError {
switch self {
@@ -89,7 +89,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
self.roomList = roomList
do {
listUpdatesSubscriptionResult = roomList.entriesWithDynamicAdapters(pageSize: UInt32(roomListPageSize), listener: RoomListEntriesListenerProxy { [weak self] updates in
listUpdatesSubscriptionResult = roomList.entriesWithDynamicAdapters(pageSize: UInt32(roomListPageSize), listener: SDKListener { [weak self] updates in
guard let self else { return }
diffsPublisher.send(updates)
})
@@ -97,7 +97,7 @@ class RoomSummaryProvider: RoomSummaryProviderProtocol {
// Forces the listener above to be called with the current state
setFilter(.all(filters: []))
let stateUpdatesSubscriptionResult = try roomList.loadingState(listener: RoomListStateObserver { [weak self] state in
let stateUpdatesSubscriptionResult = try roomList.loadingState(listener: SDKListener { [weak self] state in
guard let self else { return }
MXLog.info("\(name): Received state update: \(state)")
stateSubject.send(RoomSummaryProviderState(roomListState: state))
@@ -383,27 +383,3 @@ extension RoomSummaryProviderState {
}
}
}
private class RoomListEntriesListenerProxy: RoomListEntriesListener {
private let onUpdateClosure: ([RoomListEntriesUpdate]) -> Void
init(_ onUpdateClosure: @escaping ([RoomListEntriesUpdate]) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(roomEntriesUpdate: [RoomListEntriesUpdate]) {
onUpdateClosure(roomEntriesUpdate)
}
}
private class RoomListStateObserver: RoomListLoadingStateListener {
private let onUpdateClosure: (RoomListLoadingState) -> Void
init(_ onUpdateClosure: @escaping (RoomListLoadingState) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(state: RoomListLoadingState) {
onUpdateClosure(state)
}
}
@@ -42,7 +42,7 @@ final class RoomDirectorySearchProxy: RoomDirectorySearchProxyProtocol {
.store(in: &cancellables)
Task {
searchEntriesSubscription = await roomDirectorySearch.results(listener: RoomDirectorySearchEntriesListenerProxy { [weak self] updates in
searchEntriesSubscription = await roomDirectorySearch.results(listener: SDKListener { [weak self] updates in
self?.diffsPublisher.send(updates)
})
}
@@ -152,15 +152,3 @@ final class RoomDirectorySearchProxy: RoomDirectorySearchProxyProtocol {
canBeJoined: value.joinRule == .public || (appSettings.knockingEnabled && value.joinRule == .knock))
}
}
private final class RoomDirectorySearchEntriesListenerProxy: RoomDirectorySearchEntriesListener {
private let onUpdateClosure: ([RoomDirectorySearchEntryUpdate]) -> Void
func onUpdate(roomEntriesUpdate: [RoomDirectorySearchEntryUpdate]) {
onUpdateClosure(roomEntriesUpdate)
}
init(_ onUpdateClosure: @escaping (([RoomDirectorySearchEntryUpdate]) -> Void)) {
self.onUpdateClosure = onUpdateClosure
}
}
@@ -35,7 +35,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
init(encryption: Encryption) {
self.encryption = encryption
backupStateListenerTaskHandle = encryption.backupStateListener(listener: SecureBackupControllerListener { [weak self] state in
backupStateListenerTaskHandle = encryption.backupStateListener(listener: SDKListener { [weak self] state in
guard let self else { return }
switch state {
@@ -62,7 +62,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
}
})
recoveryStateListenerTaskHandle = encryption.recoveryStateListener(listener: SecureBackupControllerListener { [weak self] state in
recoveryStateListenerTaskHandle = encryption.recoveryStateListener(listener: SDKListener { [weak self] state in
guard let self else { return }
switch state {
@@ -121,7 +121,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
MXLog.info("Enabling recovery")
var keyUploadErrored = false
let recoveryKey = try await encryption.enableRecovery(waitForBackupsToUpload: false, passphrase: nil, progressListener: SecureBackupControllerListener { [weak self] state in
let recoveryKey = try await encryption.enableRecovery(waitForBackupsToUpload: false, passphrase: nil, progressListener: SDKListener { [weak self] state in
guard let self else { return }
switch state {
@@ -157,7 +157,7 @@ class SecureBackupController: SecureBackupControllerProtocol {
func waitForKeyBackupUpload(uploadStateSubject: CurrentValueSubject<SecureBackupSteadyState, Never>) async -> Result<Void, SecureBackupControllerError> {
do {
MXLog.info("Waiting for backup upload steady state")
try await encryption.waitForBackupUploadSteadyState(progressListener: SecureBackupControllerListener { state in
try await encryption.waitForBackupUploadSteadyState(progressListener: SDKListener { state in
let uploadState: SecureBackupSteadyState = switch state {
case .waiting: .waiting
case .uploading(let backedUpCount, let totalCount): .uploading(uploadedKeyCount: Int(backedUpCount), totalKeyCount: Int(totalCount))
@@ -210,20 +210,3 @@ class SecureBackupController: SecureBackupControllerProtocol {
}
}
}
private final class SecureBackupControllerListener<T> {
private let onUpdateClosure: (T) -> Void
init(_ onUpdateClosure: @escaping (T) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(status: T) {
onUpdateClosure(status)
}
}
extension SecureBackupControllerListener: BackupStateListener where T == BackupState { }
extension SecureBackupControllerListener: RecoveryStateListener where T == RecoveryState { }
extension SecureBackupControllerListener: EnableRecoveryProgressListener where T == EnableRecoveryProgress { }
extension SecureBackupControllerListener: BackupSteadyStateListener where T == BackupUploadState { }
@@ -57,7 +57,7 @@ class TimelineProvider: TimelineProviderProtocol {
.store(in: &cancellables)
Task {
roomTimelineObservationToken = await timeline.addListener(listener: RoomTimelineListener { [weak self] timelineDiffs in
roomTimelineObservationToken = await timeline.addListener(listener: SDKListener { [weak self] timelineDiffs in
self?.serialDispatchQueue.sync {
self?.updateItemsWithDiffs(timelineDiffs)
}
@@ -215,18 +215,6 @@ private extension VirtualTimelineItem {
}
}
private final class RoomTimelineListener: TimelineListener {
private let onUpdateClosure: ([TimelineDiff]) -> Void
init(_ onUpdateClosure: @escaping ([TimelineDiff]) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(diff: [TimelineDiff]) {
onUpdateClosure(diff)
}
}
private extension Array where Element == TimelineDiff {
var debugDescription: String {
"[" + map(\.debugDescription).joined(separator: ",") + "]"
@@ -577,7 +577,7 @@ final class TimelineProxy: TimelineProxyProtocol {
private func subscribeToPagination() async {
switch kind {
case .live:
let backPaginationListener = RoomPaginationStatusListener { [weak self] status in
let backPaginationListener = SDKListener<RoomPaginationStatus> { [weak self] status in
guard let self else {
return
}
@@ -610,32 +610,6 @@ final class TimelineProxy: TimelineProxyProtocol {
}
}
private final class RoomPaginationStatusListener: PaginationStatusListener {
private let onUpdateClosure: (RoomPaginationStatus) -> Void
init(_ onUpdateClosure: @escaping (RoomPaginationStatus) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func onUpdate(status: RoomPaginationStatus) {
onUpdateClosure(status)
}
}
private final class UploadProgressListener: ProgressWatcher {
private let onUpdateClosure: (Double) -> Void
init(_ onUpdateClosure: @escaping (Double) -> Void) {
self.onUpdateClosure = onUpdateClosure
}
func transmissionProgress(progress: TransmissionProgress) {
DispatchQueue.main.async { [weak self] in
self?.onUpdateClosure(Double(progress.current) / Double(progress.total))
}
}
}
private extension MatrixRustSDK.PollKind {
init(pollKind: Poll.Kind) {
switch pollKind {