From 8fa2d9bda414e11192eed1f47dfcf43b59b74849 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Tue, 2 Apr 2024 20:46:28 -0700 Subject: [PATCH] Convert createFeed to async await. --- Account/Sources/Account/Account.swift | 5 +- Account/Sources/Account/AccountDelegate.swift | 2 +- .../CloudKit/CloudKitAccountDelegate.swift | 16 +++++- .../Feedbin/FeedbinAccountDelegate.swift | 16 +++++- .../Feedly/FeedlyAccountDelegate.swift | 16 +++++- .../LocalAccount/LocalAccountDelegate.swift | 18 +++++++ .../NewsBlur/NewsBlurAccountDelegate.swift | 16 +++++- .../ReaderAPI/ReaderAPIAccountDelegate.swift | 16 +++++- .../AddFeed/AddFeedController.swift | 42 +++++++-------- .../AddFeed/AddFeedWindowController.swift | 4 +- .../Sidebar/SidebarOutlineDataSource.swift | 31 ++++------- Mac/Scriptability/Feed+Scriptability.swift | 14 ++--- .../ExtensionFeedAddRequestFile.swift | 7 +-- iOS/Add/AddFeedViewController.swift | 14 +++-- iOS/Feeds/FeedsViewController+Drop.swift | 51 ++++++------------- 15 files changed, 161 insertions(+), 107 deletions(-) diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index fc2054a95..80f5796c9 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -612,8 +612,9 @@ public enum FetchType { try await delegate.addFeed(for: self, with: feed, to: container) } - public func createFeed(url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { - delegate.createFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed, completion: completion) + public func createFeed(url: String, name: String?, container: Container, validateFeed: Bool) async throws -> Feed { + + try await delegate.createFeed(for: self, url: url, name: name, container: container, validateFeed: validateFeed) } func createFeed(with name: String?, url: String, feedID: String, homePageURL: String?) -> Feed { diff --git a/Account/Sources/Account/AccountDelegate.swift b/Account/Sources/Account/AccountDelegate.swift index 6b0f30872..9fb41b37e 100644 --- a/Account/Sources/Account/AccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegate.swift @@ -36,7 +36,7 @@ import Secrets func renameFolder(for account: Account, with folder: Folder, to name: String) async throws func removeFolder(for account: Account, with folder: Folder) async throws - func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) + func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool) async throws -> Feed func renameFeed(for account: Account, with feed: Feed, to name: String) async throws func addFeed(for account: Account, with: Feed, to container: Container) async throws func removeFeed(for account: Account, with feed: Feed, from container: Container) async throws diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 0c21b4fe6..80b43a7c2 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -243,7 +243,21 @@ enum CloudKitAccountDelegateError: LocalizedError { } - func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool) async throws -> Feed { + + try await withCheckedThrowingContinuation { continuation in + self.createFeed(for: account, url: url, name: name, container: container, validateFeed: validateFeed) { result in + switch result { + case .success(let feed): + continuation.resume(returning: feed) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) return diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index 3566e1313..b9f420f72 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -467,7 +467,21 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool) async throws -> Feed { + + try await withCheckedThrowingContinuation { continuation in + self.createFeed(for: account, url: url, name: name, container: container, validateFeed: validateFeed) { result in + switch result { + case .success(let feed): + continuation.resume(returning: feed) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) caller.createSubscription(url: url) { result in diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index 7fe1e0bf5..b566dc6c4 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -419,7 +419,21 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - @MainActor func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool) async throws -> Feed { + + try await withCheckedThrowingContinuation { continuation in + self.createFeed(for: account, url: url, name: name, container: container, validateFeed: validateFeed) { result in + switch result { + case .success(let feed): + continuation.resume(returning: feed) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { do { guard let credentials = credentials else { diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index 089e0d2e4..1b5fa4100 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -82,6 +82,24 @@ final class LocalAccountDelegate: AccountDelegate { } } + func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool) async throws -> Feed { + + guard let url = URL(string: urlString) else { + throw LocalAccountDelegateError.invalidParameter + } + + return try await withCheckedThrowingContinuation { continuation in + self.createRSSFeed(for: account, url: url, editedName: name, container: container) { result in + switch result { + case .success(let feed): + continuation.resume(returning: feed) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { guard let url = URL(string: urlString) else { completion(.failure(LocalAccountDelegateError.invalidParameter)) diff --git a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift index 47b561d60..57b828d67 100644 --- a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -515,7 +515,21 @@ final class NewsBlurAccountDelegate: AccountDelegate { } } - func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> ()) { + func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool) async throws -> Feed { + + try await withCheckedThrowingContinuation { continuation in + self.createFeed(for: account, url: url, name: name, container: container, validateFeed: validateFeed) { result in + switch result { + case .success(let feed): + continuation.resume(returning: feed) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> ()) { refreshProgress.addToNumberOfTasksAndRemaining(1) let folderName = (container as? Folder)?.name diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 3bf88103f..33a0582f0 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -469,7 +469,21 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { + func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool) async throws -> Feed { + + try await withCheckedThrowingContinuation { continuation in + self.createFeed(for: account, url: url, name: name, container: container, validateFeed: validateFeed) { result in + switch result { + case .success(let feed): + continuation.resume(returning: feed) + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { guard let url = URL(string: url) else { completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) return diff --git a/Mac/MainWindow/AddFeed/AddFeedController.swift b/Mac/MainWindow/AddFeed/AddFeedController.swift index c1362a9a2..cd25d088a 100644 --- a/Mac/MainWindow/AddFeed/AddFeedController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedController.swift @@ -62,31 +62,25 @@ import RSParser return } - account.createFeed(url: url.absoluteString, name: title, container: container, validateFeed: true) { result in - - DispatchQueue.main.async { - self.endShowingProgress() - } - - switch result { - case .success(let feed): - NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) - case .failure(let error): - switch error { - case AccountError.createErrorAlreadySubscribed: - self.showAlreadySubscribedError(url.absoluteString) - case AccountError.createErrorNotFound: - self.showNoFeedsErrorMessage() - default: - DispatchQueue.main.async { - NSApplication.shared.presentError(error) - } - } - } - - } - beginShowingProgress() + + Task { @MainActor in + do { + let feed = try await account.createFeed(url: url.absoluteString, name: title, container: container, validateFeed: true) + NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) + + } catch AccountError.createErrorAlreadySubscribed { + self.showAlreadySubscribedError(url.absoluteString) + + } catch AccountError.createErrorNotFound { + self.showNoFeedsErrorMessage() + + } catch { + NSApplication.shared.presentError(error) + } + + self.endShowingProgress() + } } func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController) { diff --git a/Mac/MainWindow/AddFeed/AddFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddFeedWindowController.swift index 25d3b4c9f..a46d816a1 100644 --- a/Mac/MainWindow/AddFeed/AddFeedWindowController.swift +++ b/Mac/MainWindow/AddFeed/AddFeedWindowController.swift @@ -14,8 +14,8 @@ import Account protocol AddFeedWindowControllerDelegate: AnyObject { // userEnteredURL will have already been validated and normalized. - func addFeedWindowController(_: AddFeedWindowController, userEnteredURL: URL, userEnteredTitle: String?, container: Container) - func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController) + @MainActor func addFeedWindowController(_: AddFeedWindowController, userEnteredURL: URL, userEnteredTitle: String?, container: Container) + @MainActor func addFeedWindowControllerUserDidCancel(_: AddFeedWindowController) } class AddFeedWindowController : NSWindowController { diff --git a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift index 03cf2ccea..7f9c9ae48 100644 --- a/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift +++ b/Mac/MainWindow/Sidebar/SidebarOutlineDataSource.swift @@ -346,11 +346,11 @@ private extension SidebarOutlineDataSource { func copyFeedBetweenAccounts(node: Node, to parentNode: Node) { guard let feed = node.representedObject as? Feed, - let destinationAccount = nodeAccount(parentNode), - let destinationContainer = parentNode.representedObject as? Container else { + let destinationAccount = nodeAccount(parentNode), + let destinationContainer = parentNode.representedObject as? Container else { return } - + if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) { Task { @MainActor in @@ -361,11 +361,10 @@ private extension SidebarOutlineDataSource { } } } else { - destinationAccount.createFeed(url: feed.url, name: feed.nameForDisplay, container: destinationContainer, validateFeed: false) { result in - switch result { - case .success: - break - case .failure(let error): + Task { @MainActor in + do { + try await destinationAccount.createFeed(url: feed.url, name: feed.nameForDisplay, container: destinationContainer, validateFeed: false) + } catch { NSApplication.shared.presentError(error) } } @@ -425,10 +424,10 @@ private extension SidebarOutlineDataSource { } func copyFolderBetweenAccounts(node: Node, to parentNode: Node) { - + guard let folder = node.representedObject as? Folder, - let destinationAccount = nodeAccount(parentNode) else { - return + let destinationAccount = nodeAccount(parentNode) else { + return } Task { @MainActor in @@ -440,17 +439,9 @@ private extension SidebarOutlineDataSource { if let existingFeed = destinationAccount.existingFeed(withURL: feed.url) { try await destinationAccount.addFeed(existingFeed, to: destinationFolder) } else { - destinationAccount.createFeed(url: feed.url, name: feed.nameForDisplay, container: destinationFolder, validateFeed: false) { result in - switch result { - case .success: - break - case .failure(let error): - NSApplication.shared.presentError(error) - } - } + try await destinationAccount.createFeed(url: feed.url, name: feed.nameForDisplay, container: destinationFolder, validateFeed: false) } } - } catch { NSApplication.shared.presentError(error) } diff --git a/Mac/Scriptability/Feed+Scriptability.swift b/Mac/Scriptability/Feed+Scriptability.swift index b76bad250..b289e2ec1 100644 --- a/Mac/Scriptability/Feed+Scriptability.swift +++ b/Mac/Scriptability/Feed+Scriptability.swift @@ -102,19 +102,19 @@ import Articles // suspendExecution(). When we get the callback, we supply the event result and call resumeExecution(). command.suspendExecution() - account.createFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) { result in - switch result { - case .success(let feed): + Task { @MainActor in + + do { + let feed = try await account.createFeed(url: url, name: titleFromArgs, container: container, validateFeed: true) NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) let scriptableFeed = self.scriptableFeed(feed, account:account, folder:folder) command.resumeExecution(withResult:scriptableFeed.objectSpecifier) - case .failure: + } catch { command.resumeExecution(withResult:nil) } - } - - return nil + + return nil } // MARK: --- Scriptable properties --- diff --git a/Shared/ShareExtension/ExtensionFeedAddRequestFile.swift b/Shared/ShareExtension/ExtensionFeedAddRequestFile.swift index 13152591d..9e4a29a37 100644 --- a/Shared/ShareExtension/ExtensionFeedAddRequestFile.swift +++ b/Shared/ShareExtension/ExtensionFeedAddRequestFile.swift @@ -156,7 +156,8 @@ private extension ExtensionFeedAddRequestFile { guard let container = destinationContainer else { return } - account.createFeed(url: request.feedURL.absoluteString, name: request.name, container: container, validateFeed: true) { _ in } - } - + Task { @MainActor in + try? await account.createFeed(url: request.feedURL.absoluteString, name: request.name, container: container, validateFeed: true) + } + } } diff --git a/iOS/Add/AddFeedViewController.swift b/iOS/Add/AddFeedViewController.swift index 2ff8ece0a..9c4beedc1 100644 --- a/iOS/Add/AddFeedViewController.swift +++ b/iOS/Add/AddFeedViewController.swift @@ -117,23 +117,21 @@ class AddFeedViewController: UITableViewController { BatchUpdate.shared.start() - account!.createFeed(url: url.absoluteString, name: feedName, container: container, validateFeed: true) { result in - - BatchUpdate.shared.end() - - switch result { - case .success(let feed): + Task { @MainActor in + do { + let feed = try await account!.createFeed(url: url.absoluteString, name: feedName, container: container, validateFeed: true) self.dismiss(animated: true) NotificationCenter.default.post(name: .UserDidAddFeed, object: self, userInfo: [UserInfoKey.feed: feed]) - case .failure(let error): + + } catch { self.addButton.isEnabled = true self.activityIndicator.isHidden = true self.activityIndicator.stopAnimating() self.presentError(error) } + BatchUpdate.shared.end() } - } @objc func textDidChange(_ note: Notification) { diff --git a/iOS/Feeds/FeedsViewController+Drop.swift b/iOS/Feeds/FeedsViewController+Drop.swift index b7c2282a6..a670ac17a 100644 --- a/iOS/Feeds/FeedsViewController+Drop.swift +++ b/iOS/Feeds/FeedsViewController+Drop.swift @@ -109,55 +109,36 @@ extension SidebarViewController: UITableViewDropDelegate { do { try await sourceContainer.account?.moveFeed(feed, from: sourceContainer, to: destinationContainer) - BatchUpdate.shared.end() } catch { - BatchUpdate.shared.end() self.presentError(error) } + + BatchUpdate.shared.end() } } func moveFeedBetweenAccounts(feed: Feed, sourceContainer: Container, destinationContainer: Container) { - if let existingFeed = destinationContainer.account?.existingFeed(withURL: feed.url) { + let existingFeed = destinationContainer.account?.existingFeed(withURL: feed.url) - BatchUpdate.shared.start() + BatchUpdate.shared.start() - Task { @MainActor in + Task { @MainActor in - do { + do { + if let existingFeed { try await destinationContainer.account?.addFeed(existingFeed, to: destinationContainer) - try await sourceContainer.account?.removeFeed(feed, from: sourceContainer) - BatchUpdate.shared.end() - } catch { - BatchUpdate.shared.end() - self.presentError(error) - } - } - - } else { - - BatchUpdate.shared.start() - destinationContainer.account?.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer, validateFeed: false) { result in - switch result { - case .success: - - Task { @MainActor in - do { - try await sourceContainer.account?.removeFeed(feed, from: sourceContainer) - BatchUpdate.shared.end() - } catch { - BatchUpdate.shared.end() - self.presentError(error) - } - } - - case .failure(let error): - BatchUpdate.shared.end() - self.presentError(error) - } + } else { + _ = try await destinationContainer.account?.createFeed(url: feed.url, name: feed.editedName, container: destinationContainer, validateFeed: false) } + try await sourceContainer.account?.removeFeed(feed, from: sourceContainer) + + } catch { + self.presentError(error) } + + BatchUpdate.shared.end() } + } }