From 029842d04d7492ebe88e979fee4ee4983ba00c68 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Tue, 10 Oct 2023 22:54:22 -0700 Subject: [PATCH] Convert removeFolder to async/await. --- Account/Sources/Account/Account.swift | 4 +- Account/Sources/Account/AccountDelegate.swift | 2 +- .../FeedbinAccountDelegate.swift | 96 ++++++++------- .../LocalAccountDelegate.swift | 5 +- .../NewsBlurAccountDelegate.swift | 23 ++-- .../ReaderAPIAccountDelegate.swift | 110 +++++++++--------- .../CloudKit/CloudKitAccountDelegate.swift | 94 ++++++++------- .../Feedly/FeedlyAccountDelegate.swift | 26 ++--- Mac/Scriptability/Account+Scriptability.swift | 7 +- Shared/Commands/DeleteCommand.swift | 10 +- 10 files changed, 191 insertions(+), 186 deletions(-) diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index 296ba32d6..82de92c0f 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -638,8 +638,8 @@ public enum FetchType { try await delegate.createFolder(for: self, name: name) } - public func removeFolder(_ folder: Folder, completion: @escaping (Result) -> Void) { - delegate.removeFolder(for: self, with: folder, completion: completion) + public func removeFolder(_ folder: Folder) async throws { + try await delegate.removeFolder(for: self, with: folder) } public func renameFolder(_ folder: Folder, to name: String) async throws { diff --git a/Account/Sources/Account/AccountDelegate.swift b/Account/Sources/Account/AccountDelegate.swift index 43f43ec92..227c0dd5c 100644 --- a/Account/Sources/Account/AccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegate.swift @@ -34,7 +34,7 @@ import Secrets func createFolder(for account: Account, name: String) async throws -> Folder func renameFolder(for account: Account, with folder: Folder, to name: String) async throws - func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) + 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 renameFeed(for account: Account, feed: Feed, name: String) async throws diff --git a/Account/Sources/Account/AccountDelegates/FeedbinAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/FeedbinAccountDelegate.swift index 3a8dc3e99..73d33070b 100644 --- a/Account/Sources/Account/AccountDelegates/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/FeedbinAccountDelegate.swift @@ -321,67 +321,65 @@ public enum FeedbinAccountDelegateError: String, Error { } } - func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { + func removeFolder(for account: Account, with folder: Folder) async throws { // Feedbin uses tags and if at least one feed isn't tagged, then the folder doesn't exist on their system guard folder.hasAtLeastOneFeed() else { - account.removeFolder(folder) - completion(.success(())) + try await account.removeFolder(folder) return } - let group = DispatchGroup() - - for feed in folder.topLevelFeeds { - - if feed.folderRelationship?.count ?? 0 > 1 { - - if let feedTaggingID = feed.folderRelationship?[folder.name ?? ""] { - group.enter() - refreshProgress.addToNumberOfTasksAndRemaining(1) - caller.deleteTagging(taggingID: feedTaggingID) { result in - self.refreshProgress.completeTask() - group.leave() - switch result { - case .success: - DispatchQueue.main.async { - self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") + try await withCheckedThrowingContinuation { continuation in + + let group = DispatchGroup() + + for feed in folder.topLevelFeeds { + + if feed.folderRelationship?.count ?? 0 > 1 { + + if let feedTaggingID = feed.folderRelationship?[folder.name ?? ""] { + group.enter() + refreshProgress.addToNumberOfTasksAndRemaining(1) + caller.deleteTagging(taggingID: feedTaggingID) { result in + self.refreshProgress.completeTask() + group.leave() + switch result { + case .success: + DispatchQueue.main.async { + self.clearFolderRelationship(for: feed, withFolderName: folder.name ?? "") + } + case .failure(let error): + self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") + } + } + } + + } else { + + if let subscriptionID = feed.externalID { + group.enter() + refreshProgress.addToNumberOfTasksAndRemaining(1) + caller.deleteSubscription(subscriptionID: subscriptionID) { result in + self.refreshProgress.completeTask() + group.leave() + switch result { + case .success: + DispatchQueue.main.async { + account.clearFeedMetadata(feed) + } + case .failure(let error): + self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") } - case .failure(let error): - self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") } } } - - } else { - - if let subscriptionID = feed.externalID { - group.enter() - refreshProgress.addToNumberOfTasksAndRemaining(1) - caller.deleteSubscription(subscriptionID: subscriptionID) { result in - self.refreshProgress.completeTask() - group.leave() - switch result { - case .success: - DispatchQueue.main.async { - account.clearFeedMetadata(feed) - } - case .failure(let error): - self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") - } - } - - } - } - + + group.notify(queue: DispatchQueue.main) { + account.removeFolder(folder) + continuation.resume() + } } - - group.notify(queue: DispatchQueue.main) { - account.removeFolder(folder) - completion(.success(())) - } - } func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { diff --git a/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift index 7a55d994d..a6759bd15 100644 --- a/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/LocalAccountDelegate.swift @@ -177,9 +177,8 @@ final class LocalAccountDelegate: AccountDelegate, Logging { folder.name = name } - func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { - account.removeFolder(folder) - completion(.success(())) + func removeFolder(for account: Account, with folder: Folder) async throws { + try await account.removeFolder(folder) } func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { diff --git a/Account/Sources/Account/AccountDelegates/NewsBlurAccountDelegate.swift b/Account/Sources/Account/AccountDelegates/NewsBlurAccountDelegate.swift index 50329afbe..2b7829ed6 100644 --- a/Account/Sources/Account/AccountDelegates/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/NewsBlurAccountDelegate.swift @@ -398,10 +398,9 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging { } } - func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> ()) { + func removeFolder(for account: Account, with folder: Folder) async throws { guard let folderToRemove = folder.name else { - completion(.failure(NewsBlurError.invalidParameter)) - return + throw NewsBlurError.invalidParameter } var feedIDs: [String] = [] @@ -415,15 +414,17 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging { refreshProgress.addToNumberOfTasksAndRemaining(1) - caller.removeFolder(named: folderToRemove, feedIDs: feedIDs) { result in - self.refreshProgress.completeTask() + try await withCheckedThrowingContinuation { continuation in + caller.removeFolder(named: folderToRemove, feedIDs: feedIDs) { result in + self.refreshProgress.completeTask() - switch result { - case .success: - account.removeFolder(folder) - completion(.success(())) - case .failure(let error): - completion(.failure(error)) + switch result { + case .success: + account.removeFolder(folder) + continuation.resume() + 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 229694dc1..06a37d45a 100644 --- a/Account/Sources/Account/AccountDelegates/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegates/ReaderAPIAccountDelegate.swift @@ -320,72 +320,70 @@ public enum ReaderAPIAccountDelegateError: LocalizedError { } } - func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { + func removeFolder(for account: Account, with folder: Folder) async throws { - let group = DispatchGroup() - - for feed in folder.topLevelFeeds { - - if feed.folderRelationship?.count ?? 0 > 1 { - - if let feedExternalID = feed.externalID { - group.enter() - refreshProgress.addToNumberOfTasksAndRemaining(1) - caller.deleteTagging(subscriptionID: feedExternalID, tagName: folder.nameForDisplay) { result in - self.refreshProgress.completeTask() - group.leave() - switch result { - case .success: - DispatchQueue.main.async { - self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID) + try await withCheckedThrowingContinuation { continuation in + let group = DispatchGroup() + + for feed in folder.topLevelFeeds { + + if feed.folderRelationship?.count ?? 0 > 1 { + + if let feedExternalID = feed.externalID { + group.enter() + refreshProgress.addToNumberOfTasksAndRemaining(1) + caller.deleteTagging(subscriptionID: feedExternalID, tagName: folder.nameForDisplay) { result in + self.refreshProgress.completeTask() + group.leave() + switch result { + case .success: + Task { @MainActor in + self.clearFolderRelationship(for: feed, folderExternalID: folder.externalID) + } + case .failure(let error): + self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") + } + } + } + + } else { + + if let subscriptionID = feed.externalID { + group.enter() + refreshProgress.addToNumberOfTasksAndRemaining(1) + caller.deleteSubscription(subscriptionID: subscriptionID) { result in + self.refreshProgress.completeTask() + group.leave() + switch result { + case .success: + Task { @MainActor in + account.clearFeedMetadata(feed) + } + case .failure(let error): + self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") } - case .failure(let error): - self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") } } } - - } else { - - if let subscriptionID = feed.externalID { - group.enter() - refreshProgress.addToNumberOfTasksAndRemaining(1) - caller.deleteSubscription(subscriptionID: subscriptionID) { result in - self.refreshProgress.completeTask() - group.leave() - switch result { - case .success: - DispatchQueue.main.async { - account.clearFeedMetadata(feed) - } - case .failure(let error): - self.logger.error("Remove feed error: \(error.localizedDescription, privacy: .public)") - } - } - - } - } - - } - - group.notify(queue: DispatchQueue.main) { - if self.variant == .theOldReader { - account.removeFolder(folder) - completion(.success(())) - } else { - self.caller.deleteTag(folderExternalID: folder.externalID) { result in - switch result { - case .success: - account.removeFolder(folder) - completion(.success(())) - case .failure(let error): - completion(.failure(error)) + + group.notify(queue: DispatchQueue.main) { + if self.variant == .theOldReader { + account.removeFolder(folder) + continuation.resume() + } else { + self.caller.deleteTag(folderExternalID: folder.externalID) { result in + switch result { + case .success: + account.removeFolder(folder) + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } } } } } - } func createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) { diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index bc8338a32..063778e55 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -328,58 +328,62 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging { } } - func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { - - refreshProgress.addToNumberOfTasksAndRemaining(2) - accountZone.findFeedExternalIDs(for: folder) { result in - self.refreshProgress.completeTask() - switch result { - case .success(let feedExternalIDs): + func removeFolder(for account: Account, with folder: Folder) async throws { - let feeds = feedExternalIDs.compactMap { account.existingFeed(withExternalID: $0) } - let group = DispatchGroup() - var errorOccurred = false - - for feed in feeds { - group.enter() - self.removeFeedFromCloud(for: account, with: feed, from: folder) { [weak self] result in - group.leave() - if case .failure(let error) = result { - self?.logger.error("Remove folder, remove feed error: \(error.localizedDescription, privacy: .public)") - errorOccurred = true - } - } - } - - group.notify(queue: DispatchQueue.global(qos: .background)) { - DispatchQueue.main.async { - guard !errorOccurred else { - self.refreshProgress.completeTask() - completion(.failure(CloudKitAccountDelegateError.unknown)) - return - } - - self.accountZone.removeFolder(folder) { result in - self.refreshProgress.completeTask() - switch result { - case .success: - account.removeFolder(folder) - completion(.success(())) - case .failure(let error): - completion(.failure(error)) + refreshProgress.addToNumberOfTasksAndRemaining(2) + + try await withCheckedThrowingContinuation { (continuation: CheckedContinuation) -> Void in + + accountZone.findFeedExternalIDs(for: folder) { result in + self.refreshProgress.completeTask() + switch result { + case .success(let feedExternalIDs): + + let feeds = feedExternalIDs.compactMap { account.existingFeed(withExternalID: $0) } + let group = DispatchGroup() + var errorOccurred = false + + for feed in feeds { + group.enter() + self.removeFeedFromCloud(for: account, with: feed, from: folder) { [weak self] result in + group.leave() + if case .failure(let error) = result { + self?.logger.error("Remove folder, remove feed error: \(error.localizedDescription, privacy: .public)") + errorOccurred = true } } } + + group.notify(queue: DispatchQueue.global(qos: .background)) { + DispatchQueue.main.async { + guard !errorOccurred else { + self.refreshProgress.completeTask() + continuation.resume(throwing: CloudKitAccountDelegateError.unknown) + return + } + + self.accountZone.removeFolder(folder) { result in + self.refreshProgress.completeTask() + switch result { + case .success: + account.removeFolder(folder) + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + case .failure(let error): + self.refreshProgress.completeTask() + self.refreshProgress.completeTask() + self.processAccountError(account, error) + continuation.resume(throwing: error) } - - case .failure(let error): - self.refreshProgress.completeTask() - self.refreshProgress.completeTask() - self.processAccountError(account, error) - completion(.failure(error)) } } - + } func restoreFolder(for account: Account, folder: Folder, completion: @escaping (Result) -> Void) { diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index ce4c38feb..73ed72576 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -292,25 +292,25 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging { } } - func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { + func removeFolder(for account: Account, with folder: Folder) async throws { guard let id = folder.externalID else { - return DispatchQueue.main.async { - completion(.failure(FeedlyAccountDelegateError.unableToRemoveFolder(folder.nameForDisplay))) - } + throw FeedlyAccountDelegateError.unableToRemoveFolder(folder.nameForDisplay) } let progress = refreshProgress progress.addToNumberOfTasksAndRemaining(1) - caller.deleteCollection(with: id) { result in - progress.completeTask() - - switch result { - case .success: - account.removeFolder(folder) - completion(.success(())) - case .failure(let error): - completion(.failure(error)) + try await withCheckedThrowingContinuation { continuation in + caller.deleteCollection(with: id) { result in + progress.completeTask() + + switch result { + case .success: + account.removeFolder(folder) + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } } } } diff --git a/Mac/Scriptability/Account+Scriptability.swift b/Mac/Scriptability/Account+Scriptability.swift index 3c2f2108f..65401a166 100644 --- a/Mac/Scriptability/Account+Scriptability.swift +++ b/Mac/Scriptability/Account+Scriptability.swift @@ -69,9 +69,10 @@ import RSCore func deleteElement(_ element:ScriptingObject) { if let scriptableFolder = element as? ScriptableFolder { - BatchUpdate.shared.perform { - account.removeFolder(scriptableFolder.folder) { result in - } + BatchUpdate.shared.start() + Task { @MainActor in + try? await account.removeFolder(scriptableFolder.folder) + BatchUpdate.shared.end() } } else if let scriptableFeed = element as? ScriptableFeed { BatchUpdate.shared.perform { diff --git a/Shared/Commands/DeleteCommand.swift b/Shared/Commands/DeleteCommand.swift index d952c9e0f..7942c589f 100644 --- a/Shared/Commands/DeleteCommand.swift +++ b/Shared/Commands/DeleteCommand.swift @@ -163,12 +163,16 @@ private struct SidebarItemSpecifier { } else if let folder = folder { BatchUpdate.shared.start() - account?.removeFolder(folder) { result in + Task { @MainActor in + do { + try await account?.removeFolder(folder) + } catch { + errorHandler(error) + } + BatchUpdate.shared.end() completion() - self.checkResult(result) } - } }