diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index 8d958c369..857c7e650 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -421,22 +421,9 @@ public enum FetchType { } } - public func refreshAll(completion: @escaping (Result) -> Void) { - delegate.refreshAll(for: self, completion: completion) - } - public func refreshAll() async throws { - - try await withCheckedThrowingContinuation { continuation in - self.refreshAll { result in - switch result { - case .success: - continuation.resume() - case .failure(let error): - continuation.resume(throwing: error) - } - } - } + + try await delegate.refreshAll(for: self) } public func sendArticleStatus(completion: ((Result) -> Void)? = nil) { @@ -481,7 +468,9 @@ public enum FetchType { guard let self = self else { return } // Reset the last fetch date to get the article history for the added feeds. self.metadata.lastArticleFetchStartTime = nil - self.delegate.refreshAll(for: self, completion: completion) + Task { @MainActor in + try? await self.refreshAll() + } case .failure(let error): completion(.failure(error)) } diff --git a/Account/Sources/Account/AccountDelegate.swift b/Account/Sources/Account/AccountDelegate.swift index 5de6ca9cf..211348eae 100644 --- a/Account/Sources/Account/AccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegate.swift @@ -25,7 +25,7 @@ import Secrets func receiveRemoteNotification(for account: Account, userInfo: [AnyHashable : Any], completion: @escaping () -> Void) - func refreshAll(for account: Account, completion: @escaping (Result) -> Void) + func refreshAll(for account: Account) async throws func syncArticleStatus(for account: Account) async throws func sendArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) func refreshArticleStatus(for account: Account, completion: @escaping ((Result) -> Void)) diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 3a2e4c203..a1247a9f5 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -79,20 +79,28 @@ enum CloudKitAccountDelegateError: LocalizedError { } } - func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { + func refreshAll(for account: Account) async throws { + guard refreshProgress.isComplete else { - completion(.success(())) return } let reachability = SCNetworkReachabilityCreateWithName(nil, "apple.com") var flags = SCNetworkReachabilityFlags() guard SCNetworkReachabilityGetFlags(reachability!, &flags), flags.contains(.reachable) else { - completion(.success(())) return } - standardRefreshAll(for: account, completion: completion) + try await withCheckedThrowingContinuation { continuation in + self.standardRefreshAll(for: account) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } } func syncArticleStatus(for account: Account) async throws { @@ -582,15 +590,9 @@ private extension CloudKitAccountDelegate { func combinedRefresh(_ account: Account, _ feeds: Set, completion: @escaping (Result) -> Void) { - let group = DispatchGroup() - - group.enter() - refresher.refreshFeeds(feeds) { - group.leave() - } - - group.notify(queue: DispatchQueue.main) { - completion(.success(())) + Task { @MainActor in + await self.refresher.refreshFeeds(feeds) + completion(.success(())) } } diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index 8ad324c16..3782e7102 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -79,37 +79,38 @@ final class FeedbinAccountDelegate: AccountDelegate { completion() } - func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { + func refreshAll(for account: Account) async throws { refreshProgress.addToNumberOfTasksAndRemaining(5) - - refreshAccount(account) { result in - switch result { - case .success(): - self.refreshArticlesAndStatuses(account) { result in - switch result { - case .success(): - completion(.success(())) - case .failure(let error): - DispatchQueue.main.async { - self.refreshProgress.clear() - let wrappedError = AccountError.wrappedError(error: error, account: account) - completion(.failure(wrappedError)) + try await withCheckedThrowingContinuation { continuation in + + refreshAccount(account) { result in + switch result { + case .success(): + + self.refreshArticlesAndStatuses(account) { result in + switch result { + case .success(): + continuation.resume() + case .failure(let error): + DispatchQueue.main.async { + self.refreshProgress.clear() + let wrappedError = AccountError.wrappedError(error: error, account: account) + continuation.resume(throwing: wrappedError) + } } } - } - - case .failure(let error): - DispatchQueue.main.async { - self.refreshProgress.clear() - let wrappedError = AccountError.wrappedError(error: error, account: account) - completion(.failure(wrappedError)) + + case .failure(let error): + DispatchQueue.main.async { + self.refreshProgress.clear() + let wrappedError = AccountError.wrappedError(error: error, account: account) + continuation.resume(throwing: wrappedError) + } } } - } - } func syncArticleStatus(for account: Account) async throws { diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index a328a381c..066711a1f 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -108,7 +108,21 @@ final class FeedlyAccountDelegate: AccountDelegate { completion() } - @MainActor func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { + func refreshAll(for account: Account) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.refreshAll(for: account) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { assert(Thread.isMainThread) guard currentSyncAllOperation == nil else { diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index a108b5f40..930e8a6f8 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -25,7 +25,7 @@ final class LocalAccountDelegate: AccountDelegate { weak var account: Account? - private lazy var refresher: LocalAccountRefresher? = { + private lazy var refresher: LocalAccountRefresher = { let refresher = LocalAccountRefresher() refresher.delegate = self return refresher @@ -44,28 +44,19 @@ final class LocalAccountDelegate: AccountDelegate { completion() } - func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { + func refreshAll(for account: Account) async throws { + guard refreshProgress.isComplete else { - completion(.success(())) return } let feeds = account.flattenedFeeds() refreshProgress.addToNumberOfTasksAndRemaining(feeds.count) - let group = DispatchGroup() + await refresher.refreshFeeds(feeds) - group.enter() - refresher?.refreshFeeds(feeds) { - group.leave() - } - - group.notify(queue: DispatchQueue.main) { - self.refreshProgress.clear() - account.metadata.lastArticleFetchEndTime = Date() - completion(.success(())) - } - + self.refreshProgress.clear() + account.metadata.lastArticleFetchEndTime = Date() } func syncArticleStatus(for account: Account) async throws { @@ -205,7 +196,7 @@ final class LocalAccountDelegate: AccountDelegate { // MARK: Suspend and Resume (for iOS) func suspendNetwork() { - refresher?.suspend() + refresher.suspend() } func suspendDatabase() { @@ -213,7 +204,7 @@ final class LocalAccountDelegate: AccountDelegate { } func resume() { - refresher?.resume() + refresher.resume() } } diff --git a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift index e685266d7..37c7e1a3e 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountRefresher.swift @@ -28,7 +28,7 @@ final class LocalAccountRefresher { return DownloadSession(delegate: self) }() - public func refreshFeeds(_ feeds: Set, completion: (() -> Void)? = nil) { + private func refreshFeeds(_ feeds: Set, completion: (() -> Void)? = nil) { guard !feeds.isEmpty else { completion?() return @@ -37,6 +37,15 @@ final class LocalAccountRefresher { downloadSession.downloadObjects(feeds as NSSet) } + public func refreshFeeds(_ feeds: Set) async { + + await withCheckedContinuation { continuation in + self.refreshFeeds(feeds) { + continuation.resume() + } + } + } + public func suspend() { downloadSession.cancelAll() isSuspended = true diff --git a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift index 4e56f490a..9e2ddce22 100644 --- a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -61,7 +61,22 @@ final class NewsBlurAccountDelegate: AccountDelegate { completion() } - func refreshAll(for account: Account, completion: @escaping (Result) -> ()) { + func refreshAll(for account: Account) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.refreshAll(for: account) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func refreshAll(for account: Account, completion: @escaping (Result) -> ()) { self.refreshProgress.addToNumberOfTasksAndRemaining(4) refreshFeeds(for: account) { result in diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 2e387de34..a49798d0c 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -105,7 +105,21 @@ final class ReaderAPIAccountDelegate: AccountDelegate { completion() } - func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { + func refreshAll(for account: Account) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.refreshAll(for: account) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func refreshAll(for account: Account, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(6) refreshAccount(account) { result in diff --git a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift index 07c29cccf..f89397937 100644 --- a/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsFeedbinWindowController.swift @@ -102,16 +102,15 @@ class AccountsFeedbinWindowController: NSWindowController { 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): + + Task { @MainActor in + do { + try await self.account?.refreshAll() + } catch { NSApplication.shared.presentError(error) } } - + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") diff --git a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift index 266cae8c9..026b50d90 100644 --- a/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsNewsBlurWindowController.swift @@ -101,15 +101,14 @@ class AccountsNewsBlurWindowController: NSWindowController { try self.account?.storeCredentials(credentials) try self.account?.storeCredentials(validatedCredentials) - self.account?.refreshAll() { result in - switch result { - case .success: - break - case .failure(let error): + Task { @MainActor in + do { + try await self.account?.refreshAll() + } catch { NSApplication.shared.presentError(error) } } - + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") diff --git a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift index 928deb5cf..244cce6e6 100644 --- a/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift +++ b/Mac/Preferences/Accounts/AccountsPreferencesViewController.swift @@ -271,11 +271,10 @@ extension AccountsPreferencesViewController: OAuthAccountAuthorizationOperationD // because the user probably wants to see the result of authorizing NetNewsWire to act on their behalf. NSApp.activate(ignoringOtherApps: true) - account.refreshAll { [weak self] result in - switch result { - case .success: - break - case .failure(let error): + Task { @MainActor [weak self] in + do { + try await account.refreshAll() + } catch { self?.presentError(error) } } diff --git a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift index e876df380..71fc0e469 100644 --- a/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift +++ b/Mac/Preferences/Accounts/AccountsReaderAPIWindowController.swift @@ -157,16 +157,15 @@ class AccountsReaderAPIWindowController: NSWindowController { 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): + + Task { @MainActor in + do { + try await self.account?.refreshAll() + } catch { NSApplication.shared.presentError(error) } } - + self.hostWindow?.endSheet(self.window!, returnCode: NSApplication.ModalResponse.OK) } catch { self.errorMessageLabel.stringValue = NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error") diff --git a/iOS/Account/FeedbinAccountViewController.swift b/iOS/Account/FeedbinAccountViewController.swift index 296d7e96f..85c560f1c 100644 --- a/iOS/Account/FeedbinAccountViewController.swift +++ b/iOS/Account/FeedbinAccountViewController.swift @@ -139,16 +139,15 @@ class FeedbinAccountViewController: UITableViewController { try self.account?.removeCredentials(type: .basic) } catch {} try self.account?.storeCredentials(credentials) - - self.account?.refreshAll() { result in - switch result { - case .success: - break - case .failure(let error): + + Task { @MainActor in + do { + try await self.account?.refreshAll() + } catch { self.presentError(error) } } - + self.dismiss(animated: true, completion: nil) self.delegate?.dismiss() } catch { diff --git a/iOS/Account/NewsBlurAccountViewController.swift b/iOS/Account/NewsBlurAccountViewController.swift index baa6abda7..33dfb5450 100644 --- a/iOS/Account/NewsBlurAccountViewController.swift +++ b/iOS/Account/NewsBlurAccountViewController.swift @@ -127,11 +127,10 @@ class NewsBlurAccountViewController: UITableViewController { try self.account?.storeCredentials(basicCredentials) try self.account?.storeCredentials(sessionCredentials) - self.account?.refreshAll() { result in - switch result { - case .success: - break - case .failure(let error): + Task { @MainActor in + do { + try await self.account?.refreshAll() + } catch { self.presentError(error) } } diff --git a/iOS/Account/ReaderAPIAccountViewController.swift b/iOS/Account/ReaderAPIAccountViewController.swift index d8fdd9ade..fa1847bca 100644 --- a/iOS/Account/ReaderAPIAccountViewController.swift +++ b/iOS/Account/ReaderAPIAccountViewController.swift @@ -180,15 +180,14 @@ class ReaderAPIAccountViewController: UITableViewController { self.dismiss(animated: true, completion: nil) - self.account?.refreshAll() { result in - switch result { - case .success: - break - case .failure(let error): + Task { @MainActor in + do { + try await self.account?.refreshAll() + } catch { self.showError(NSLocalizedString(error.localizedDescription, comment: "Accoount Refresh Error")) } } - + self.delegate?.dismiss() } catch { self.showError(NSLocalizedString("Keychain error while storing credentials.", comment: "Credentials Error")) diff --git a/iOS/Settings/AddAccountViewController.swift b/iOS/Settings/AddAccountViewController.swift index 42a1b2a35..4780dace6 100644 --- a/iOS/Settings/AddAccountViewController.swift +++ b/iOS/Settings/AddAccountViewController.swift @@ -226,20 +226,17 @@ class AddAccountViewController: UITableViewController, AddAccountDismissDelegate extension AddAccountViewController: OAuthAccountAuthorizationOperationDelegate { func oauthAccountAuthorizationOperation(_ operation: OAuthAccountAuthorizationOperation, didCreate account: Account) { + let rootViewController = view.window?.rootViewController - - account.refreshAll { result in - switch result { - case .success: - break - case .failure(let error): - guard let viewController = rootViewController else { - return - } - viewController.presentError(error) + + Task { @MainActor in + do { + try await account.refreshAll() + } catch { + rootViewController?.presentError(error) } } - + dismiss() }