From f042c9715669ee5fe42e3f577df400e18a5c8c60 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Sat, 7 Oct 2023 10:53:02 -0700 Subject: [PATCH] Convert validateCredentials to async/await. --- Account/Sources/Account/Account.swift | 10 +-- Account/Sources/Account/AccountDelegate.swift | 2 +- .../FeedbinAccountDelegate.swift | 15 ++-- .../LocalAccountDelegate.swift | 4 +- .../NewsBlurAccountDelegate.swift | 15 +++- .../ReaderAPIAccountDelegate.swift | 20 +++-- .../CloudKit/CloudKitAccountDelegate.swift | 4 +- .../Feedly/FeedlyAccountDelegate.swift | 4 +- .../AccountsFeedbinWindowController.swift | 87 +++++++++---------- .../AccountsNewsBlurWindowController.swift | 73 ++++++++-------- .../AccountsReaderAPIWindowController.swift | 84 +++++++++--------- 11 files changed, 159 insertions(+), 159 deletions(-) diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index 576de244e..dd0f4dc16 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -373,16 +373,16 @@ public enum FetchType { try CredentialsManager.removeCredentials(type: type, server: server, username: username) } - public static func validateCredentials(transport: Transport = URLSession.webserviceTransport(), type: AccountType, credentials: Credentials, endpoint: URL? = nil, completion: @escaping (Result) -> Void) { + public static func validateCredentials(transport: Transport = URLSession.webserviceTransport(), type: AccountType, credentials: Credentials, endpoint: URL? = nil) async throws -> Credentials? { switch type { case .feedbin: - FeedbinAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion) + return try await FeedbinAccountDelegate.validateCredentials(transport: transport, credentials: credentials) case .newsBlur: - NewsBlurAccountDelegate.validateCredentials(transport: transport, credentials: credentials, completion: completion) + return try await NewsBlurAccountDelegate.validateCredentials(transport: transport, credentials: credentials) case .freshRSS, .inoreader, .bazQux, .theOldReader: - ReaderAPIAccountDelegate.validateCredentials(transport: transport, credentials: credentials, endpoint: endpoint, completion: completion) + return try await ReaderAPIAccountDelegate.validateCredentials(transport: transport, credentials: credentials, endpoint: endpoint) default: - break + return nil } } diff --git a/Account/Sources/Account/AccountDelegate.swift b/Account/Sources/Account/AccountDelegate.swift index 2ce4df5ec..fbbe0333b 100644 --- a/Account/Sources/Account/AccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegate.swift @@ -53,7 +53,7 @@ import Secrets func accountWillBeDeleted(_ account: Account) - static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?) async throws -> Credentials? /// Suspend all network activity func suspendNetwork() diff --git a/Account/Sources/Account/AccountDelegates/FeedbinAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/FeedbinAccountDelegate.swift index d3149a7b5..0ebda2bbb 100644 --- a/Account/Sources/Account/AccountDelegates/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/FeedbinAccountDelegate.swift @@ -612,16 +612,21 @@ public enum FeedbinAccountDelegateError: String, Error { func accountWillBeDeleted(_ account: Account) { } - static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: @escaping (Result) -> Void) { + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil) async throws -> Credentials? { let caller = FeedbinAPICaller(transport: transport) caller.credentials = credentials - caller.validateCredentials() { result in - DispatchQueue.main.async { - completion(result) + + return try await withCheckedThrowingContinuation { continuation in + caller.validateCredentials() { result in + switch result { + case .success(let credentials): + continuation.resume(returning: credentials) + case .failure(let error): + continuation.resume(throwing: error) + } } } - } // MARK: Suspend and Resume (for iOS) diff --git a/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift index c425e2d0a..f3608dcc0 100644 --- a/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift @@ -205,8 +205,8 @@ final class LocalAccountDelegate: AccountDelegate, Logging { func accountWillBeDeleted(_ account: Account) { } - static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: (Result) -> Void) { - return completion(.success(nil)) + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil) async throws -> Credentials? { + nil } // MARK: Suspend and Resume (for iOS) diff --git a/Account/Sources/Account/AccountDelegates/NewsBlurAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/NewsBlurAccountDelegate.swift index 38b3e832b..7e3a2bb86 100644 --- a/Account/Sources/Account/AccountDelegates/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/NewsBlurAccountDelegate.swift @@ -649,14 +649,21 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging { caller.logout() { _ in } } - class func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: @escaping (Result) -> ()) { + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil) async throws -> Credentials? { + let caller = NewsBlurAPICaller(transport: transport) { url, credentials in URLRequest(url: url, credentials: credentials) } caller.credentials = credentials - caller.validateCredentials() { result in - DispatchQueue.main.async { - completion(result) + + return try await withCheckedThrowingContinuation { continuation in + caller.validateCredentials() { result in + switch result { + case .success(let credentials): + continuation.resume(returning: credentials) + case .failure(let error): + continuation.resume(throwing: error) + } } } } diff --git a/Account/Sources/Account/AccountDelegates/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/ReaderAPIAccountDelegate.swift index dcf6c6992..d7723ec69 100644 --- a/Account/Sources/Account/AccountDelegates/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/ReaderAPIAccountDelegate.swift @@ -660,17 +660,21 @@ public enum ReaderAPIAccountDelegateError: LocalizedError { func accountWillBeDeleted(_ account: Account) { } - static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) { + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil) async throws -> Credentials? { guard let endpoint = endpoint else { - completion(.failure(TransportError.noURL)) - return + throw TransportError.noURL } - ReaderAPICaller.validateCredentials(credentials: credentials, transport: transport, endpoint: endpoint, variant: .generic) { url, credentials in - URLRequest(url: url, credentials: credentials) - } completion: { result in - Task { @MainActor in - completion(result) + return try await withCheckedThrowingContinuation { continuation in + ReaderAPICaller.validateCredentials(credentials: credentials, transport: transport, endpoint: endpoint, variant: .generic) { url, credentials in + URLRequest(url: url, credentials: credentials) + } completion: { result in + switch result { + case .success(let credentials): + continuation.resume(returning: credentials) + case .failure(let error): + continuation.resume(throwing: error) + } } } } diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 20ec14bb3..1815c419b 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -474,8 +474,8 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging { articlesZone.resetChangeToken() } - static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, completion: (Result) -> Void) { - return completion(.success(nil)) + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil) async throws -> Credentials? { + nil } // MARK: Suspend and Resume (for iOS) diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index b2a50dc72..62de5cf96 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -573,9 +573,9 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging { MainThreadOperationQueue.shared.add(logout) } - static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, completion: @escaping (Result) -> Void) { + static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil) async throws -> Credentials? { assertionFailure("An `account` instance should enqueue an \(FeedlyRefreshAccessTokenOperation.self) instead.") - completion(.success(credentials)) + return credentials } // MARK: Suspend and Resume (for iOS) diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift index 3ca520f6a..61328690d 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift @@ -62,73 +62,64 @@ import Secrets } @IBAction func action(_ sender: Any) { - + self.errorMessageLabel.stringValue = "" - + guard !usernameTextField.stringValue.isEmpty && !passwordTextField.stringValue.isEmpty else { self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.userNameAndPasswordRequired.localizedDescription - + return } - + guard account != nil || !AccountManager.shared.duplicateServiceAccount(type: .feedbin, username: usernameTextField.stringValue) else { self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.duplicateAccount.localizedDescription return } - + actionButton.isEnabled = false progressIndicator.isHidden = false progressIndicator.startAnimation(self) - - let credentials = Credentials(type: .basic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue) - Account.validateCredentials(type: .feedbin, credentials: credentials) { [weak self] result in - - guard let self = self else { return } - - self.actionButton.isEnabled = true - self.progressIndicator.isHidden = true - self.progressIndicator.stopAnimation(self) - - switch result { - case .success(let validatedCredentials): - - guard let validatedCredentials = validatedCredentials else { - self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidUsernameOrPassword.localizedDescription - - return - } - - if self.account == nil { - self.account = AccountManager.shared.createAccount(type: .feedbin) - } - - do { - try self.account?.removeCredentials(type: .basic) - try self.account?.storeCredentials(validatedCredentials) - self.account?.refreshAll() { result in - switch result { - case .success: - break - case .failure(let error): - NSApplication.shared.presentError(error) - } + let credentials = Credentials(type: .basic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue) + + Task { @MainActor in + do { + let validatedCredentials = try await Account.validateCredentials(type: .feedbin, credentials: credentials) + if let validatedCredentials { + if self.account == nil { + self.account = AccountManager.shared.createAccount(type: .feedbin) } - self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) - } catch { - self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.keychainError.localizedDescription - self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)") + do { + try self.account?.removeCredentials(type: .basic) + try self.account?.storeCredentials(validatedCredentials) + + self.account?.refreshAll() { result in + switch result { + case .success: + break + case .failure(let error): + NSApplication.shared.presentError(error) + } + } + + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) + } catch { + self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.keychainError.localizedDescription + self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)") + } } - - case .failure: - + else { + self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidUsernameOrPassword.localizedDescription + } + } catch { self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.networkError.localizedDescription - } - + + actionButton.isEnabled = true + progressIndicator.isHidden = true + progressIndicator.stopAnimation(self) } - } @IBAction func createAccountWithProvider(_ sender: Any) { diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift index 043dc68df..fe9602069 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift @@ -77,49 +77,46 @@ import Secrets progressIndicator.startAnimation(self) let credentials = Credentials(type: .newsBlurBasic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue) - Account.validateCredentials(type: .newsBlur, credentials: credentials) { [weak self] result in - guard let self = self else { return } + Task { @MainActor in + do { + let validatedCredentials = try await Account.validateCredentials(type: .newsBlur, credentials: credentials) + if let validatedCredentials { + if self.account == nil { + self.account = AccountManager.shared.createAccount(type: .newsBlur) + } + + do { + try self.account?.removeCredentials(type: .newsBlurBasic) + try self.account?.removeCredentials(type: .newsBlurSessionID) + try self.account?.storeCredentials(credentials) + try self.account?.storeCredentials(validatedCredentials) + + self.account?.refreshAll() { result in + switch result { + case .success: + break + case .failure(let error): + NSApplication.shared.presentError(error) + } + } + + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) + } catch { + self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.keychainError.localizedDescription + self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)") + } + } + else { + self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidUsernameOrPassword.localizedDescription + } + } catch { + self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.networkError.localizedDescription + } self.actionButton.isEnabled = true self.progressIndicator.isHidden = true self.progressIndicator.stopAnimation(self) - - switch result { - case .success(let validatedCredentials): - guard let validatedCredentials = validatedCredentials else { - self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidUsernameOrPassword.localizedDescription - return - } - - if self.account == nil { - self.account = AccountManager.shared.createAccount(type: .newsBlur) - } - - do { - try self.account?.removeCredentials(type: .newsBlurBasic) - try self.account?.removeCredentials(type: .newsBlurSessionID) - try self.account?.storeCredentials(credentials) - try self.account?.storeCredentials(validatedCredentials) - - self.account?.refreshAll() { result in - switch result { - case .success: - break - case .failure(let error): - NSApplication.shared.presentError(error) - } - } - - self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) - } catch { - self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.keychainError.localizedDescription - self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)") - } - - case .failure: - self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.networkError.localizedDescription - } } } diff --git a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift index e57f53b08..7f5b52031 100644 --- a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift @@ -133,54 +133,50 @@ import ReaderAPI progressIndicator.startAnimation(self) let credentials = Credentials(type: .readerBasic, username: usernameTextField.stringValue, secret: passwordTextField.stringValue) - Account.validateCredentials(type: accountType, credentials: credentials, endpoint: apiURL) { [weak self] result in - - guard let self = self else { return } - + + Task { @MainActor in + + do { + let validatedCredentials = try await Account.validateCredentials(type: accountType, credentials: credentials, endpoint: apiURL) + if let validatedCredentials { + if self.account == nil { + self.account = AccountManager.shared.createAccount(type: self.accountType!) + } + + do { + self.account?.endpointURL = apiURL + + try self.account?.removeCredentials(type: .readerBasic) + try self.account?.removeCredentials(type: .readerAPIKey) + try self.account?.storeCredentials(credentials) + try self.account?.storeCredentials(validatedCredentials) + + self.account?.refreshAll() { result in + switch result { + case .success: + break + case .failure(let error): + NSApplication.shared.presentError(error) + } + } + + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) + } catch { + self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.keychainError.localizedDescription + self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)") + } + } + else { + self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidUsernameOrPassword.localizedDescription + } + } catch { + self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.networkError.localizedDescription + } + self.actionButton.isEnabled = true self.progressIndicator.isHidden = true self.progressIndicator.stopAnimation(self) - - switch result { - case .success(let validatedCredentials): - guard let validatedCredentials = validatedCredentials else { - self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.invalidUsernameOrPassword.localizedDescription - return - } - - if self.account == nil { - self.account = AccountManager.shared.createAccount(type: self.accountType!) - } - - do { - self.account?.endpointURL = apiURL - - try self.account?.removeCredentials(type: .readerBasic) - try self.account?.removeCredentials(type: .readerAPIKey) - try self.account?.storeCredentials(credentials) - try self.account?.storeCredentials(validatedCredentials) - - self.account?.refreshAll() { result in - switch result { - case .success: - break - case .failure(let error): - NSApplication.shared.presentError(error) - } - } - - self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) - } catch { - self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.keychainError.localizedDescription - self.logger.error("Keychain error while storing credentials: \(error.localizedDescription, privacy: .public)") - } - - case .failure: - self.errorMessageLabel.stringValue = LocalizedNetNewsWireError.networkError.localizedDescription - } - } - } @IBAction func createAccountWithProvider(_ sender: Any) {