diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index 9f18ec702..606d3c994 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -631,8 +631,9 @@ public enum FetchType { delegate.moveFeed(for: self, with: feed, from: from, to: to, completion: completion) } - public func renameFeed(_ feed: Feed, to name: String, completion: @escaping (Result) -> Void) { - delegate.renameFeed(for: self, with: feed, to: name, completion: completion) + public func renameFeed(_ feed: Feed, to name: String) async throws { + + try await delegate.renameFeed(for: self, with: feed, to: name) } public func restoreFeed(_ feed: Feed, container: Container) async throws { @@ -649,11 +650,13 @@ public enum FetchType { delegate.removeFolder(for: self, with: folder, completion: completion) } - public func renameFolder(_ folder: Folder, to name: String, completion: @escaping (Result) -> Void) { - delegate.renameFolder(for: self, with: folder, to: name, completion: completion) + public func renameFolder(_ folder: Folder, to name: String) async throws { + + try await delegate.renameFolder(for: self, with: folder, to: name) } public func restoreFolder(_ folder: Folder) async throws { + try await delegate.restoreFolder(for: self, folder: folder) } diff --git a/Account/Sources/Account/AccountDelegate.swift b/Account/Sources/Account/AccountDelegate.swift index 35c938bbb..de45a7ac1 100644 --- a/Account/Sources/Account/AccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegate.swift @@ -33,11 +33,11 @@ import Secrets func importOPML(for account:Account, opmlFile: URL) async throws func createFolder(for account: Account, name: String) async throws -> Folder - func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) + 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 createFeed(for account: Account, url: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result) -> Void) - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) + func renameFeed(for account: Account, with feed: Feed, to name: String) async throws func addFeed(for account: Account, with: Feed, to container: Container, completion: @escaping (Result) -> Void) func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) func moveFeed(for account: Account, with feed: Feed, from: Container, to: Container, completion: @escaping (Result) -> Void) diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 0f119e90c..f81f4c452 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -254,7 +254,22 @@ enum CloudKitAccountDelegateError: LocalizedError { createRSSFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion) } - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { + func renameFeed(for account: Account, with feed: Feed, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFeed(for: account, with: feed, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { let editedName = name.isEmpty ? nil : name refreshProgress.addToNumberOfTasksAndRemaining(1) accountZone.renameFeed(feed, editedName: editedName) { result in @@ -370,7 +385,22 @@ enum CloudKitAccountDelegateError: LocalizedError { } } - func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { + func renameFolder(for account: Account, with folder: Folder, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFolder(for: account, with: folder, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) accountZone.renameFolder(folder, to: name) { result in self.refreshProgress.completeTask() diff --git a/Account/Sources/Account/Feed.swift b/Account/Sources/Account/Feed.swift index a2ea34164..254de9df7 100644 --- a/Account/Sources/Account/Feed.swift +++ b/Account/Sources/Account/Feed.swift @@ -189,9 +189,13 @@ import Core // MARK: - Renamable - public func rename(to newName: String, completion: @escaping (Result) -> Void) { - guard let account = account else { return } - account.renameFeed(self, to: newName, completion: completion) + public func rename(to newName: String) async throws { + + guard let account else { + return + } + + try await account.renameFeed(self, to: newName) } // MARK: - UnreadCountProvider diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index 5342ac2c3..3bf478b5c 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -347,7 +347,22 @@ final class FeedbinAccountDelegate: AccountDelegate { return folder } - func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { + func renameFolder(for account: Account, with folder: Folder, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFolder(for: account, with: folder, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { guard folder.hasAtLeastOneFeed() else { folder.name = name @@ -469,7 +484,22 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { + func renameFeed(for account: Account, with feed: Feed, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFeed(for: account, with: feed, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { // This error should never happen guard let subscriptionID = feed.externalID else { @@ -1152,17 +1182,19 @@ private extension FeedbinAccountDelegate { feed.externalID = String(sub.subscriptionID) feed.iconURL = sub.jsonFeed?.icon feed.faviconURL = sub.jsonFeed?.favicon - + account.addFeed(feed, to: container) { result in switch result { case .success: if let name = name { - account.renameFeed(feed, to: name) { result in - switch result { - case .success: + + Task { @MainActor in + do { + try await account.renameFeed(feed, to: name) self.initialFeedDownload(account: account, feed: feed, completion: completion) - case .failure(let error): + } catch { completion(.failure(error)) + } } } else { @@ -1172,9 +1204,7 @@ private extension FeedbinAccountDelegate { completion(.failure(error)) } } - } - } func initialFeedDownload( account: Account, feed: Feed, completion: @escaping (Result) -> Void) { diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index a776adc61..d20c3724b 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -343,7 +343,22 @@ final class FeedlyAccountDelegate: AccountDelegate { } } - func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { + func renameFolder(for account: Account, with folder: Folder, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFolder(for: account, with: folder, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { guard let id = folder.externalID else { return DispatchQueue.main.async { completion(.failure(FeedlyAccountDelegateError.unableToRenameFolder(folder.nameForDisplay, name))) @@ -422,6 +437,21 @@ final class FeedlyAccountDelegate: AccountDelegate { } } + func renameFeed(for account: Account, with feed: Feed, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFeed(for: account, with: feed, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { let folderCollectionIds = account.folders?.filter { $0.has(feed) }.compactMap { $0.externalID } guard let collectionIds = folderCollectionIds, let collectionId = collectionIds.first else { diff --git a/Account/Sources/Account/Folder.swift b/Account/Sources/Account/Folder.swift index 713496ede..8f185ec69 100644 --- a/Account/Sources/Account/Folder.swift +++ b/Account/Sources/Account/Folder.swift @@ -53,9 +53,13 @@ import Core // MARK: - Renamable - public func rename(to name: String, completion: @escaping (Result) -> Void) { - guard let account = account else { return } - account.renameFolder(self, to: name, completion: completion) + public func rename(to name: String) async throws { + + guard let account else { + return + } + + try await account.renameFolder(self, to: name) } // MARK: - Init diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index 0bb6271bc..5fea4ce27 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -91,9 +91,9 @@ final class LocalAccountDelegate: AccountDelegate { createRSSFeed(for: account, url: url, editedName: name, container: container, completion: completion) } - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { + func renameFeed(for account: Account, with feed: Feed, to name: String) async throws { + feed.editedName = name - completion(.success(())) } func removeFeed(for account: Account, with feed: Feed, from container: Container, completion: @escaping (Result) -> Void) { @@ -125,9 +125,9 @@ final class LocalAccountDelegate: AccountDelegate { return folder } - func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { + func renameFolder(for account: Account, with folder: Folder, to name: String) async throws { + folder.name = name - completion(.success(())) } func removeFolder(for account: Account, with folder: Folder, completion: @escaping (Result) -> Void) { diff --git a/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift b/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift index de5b24a25..65846f7c8 100644 --- a/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift +++ b/Account/Sources/Account/NewsBlur/Internals/NewsBlurAccountDelegate+Internal.swift @@ -443,11 +443,12 @@ extension NewsBlurAccountDelegate { switch result { case .success: if let name = name { - account.renameFeed(feed, to: name) { result in - switch result { - case .success: + + Task { @MainActor in + do { + try await account.renameFeed(feed, to: name) self.initialFeedDownload(account: account, feed: feed, completion: completion) - case .failure(let error): + } catch { completion(.failure(error)) } } diff --git a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift index 1926ae612..c0db2e6df 100644 --- a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -430,7 +430,22 @@ final class NewsBlurAccountDelegate: AccountDelegate { } } - func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> ()) { + func renameFolder(for account: Account, with folder: Folder, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFolder(for: account, with: folder, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> ()) { guard let folderToRename = folder.name else { completion(.failure(NewsBlurError.invalidParameter)) return @@ -504,7 +519,22 @@ final class NewsBlurAccountDelegate: AccountDelegate { } } - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> ()) { + func renameFeed(for account: Account, with feed: Feed, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFeed(for: account, with: feed, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> ()) { guard let feedID = feed.externalID else { completion(.failure(NewsBlurError.invalidParameter)) return diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 8853d07cf..a24acacdd 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -349,7 +349,22 @@ final class ReaderAPIAccountDelegate: AccountDelegate { return folder } - func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { + func renameFolder(for account: Account, with folder: Folder, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFolder(for: account, with: folder, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) { refreshProgress.addToNumberOfTasksAndRemaining(1) caller.renameTag(oldName: folder.name ?? "", newName: name) { result in @@ -488,8 +503,23 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } - func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { - + func renameFeed(for account: Account, with feed: Feed, to name: String) async throws { + + try await withCheckedThrowingContinuation { continuation in + + self.renameFeed(for: account, with: feed, to: name) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { + // This error should never happen guard let subscriptionID = feed.externalID else { completion(.failure(ReaderAPIAccountDelegateError.invalidParameter)) diff --git a/Core/Sources/Core/Renamable.swift b/Core/Sources/Core/Renamable.swift index e65816b20..ce447feee 100644 --- a/Core/Sources/Core/Renamable.swift +++ b/Core/Sources/Core/Renamable.swift @@ -17,7 +17,8 @@ public protocol Renamable { /// - to: The new name for the object. /// - completion: A block called when the renaming completes or fails. /// - result: The result of the renaming. - func rename(to: String, completion: @escaping (_ result: Result) -> Void) + @MainActor func rename(to: String) async throws + } diff --git a/Mac/Inspector/FeedInspectorViewController.swift b/Mac/Inspector/FeedInspectorViewController.swift index ac25e29fa..f1d29818a 100644 --- a/Mac/Inspector/FeedInspectorViewController.swift +++ b/Mac/Inspector/FeedInspectorViewController.swift @@ -203,20 +203,22 @@ private extension FeedInspectorViewController { } func renameFeedIfNecessary() { - guard let feed = feed, + + guard let feed, let account = feed.account, - let nameTextField = nameTextField, - feed.nameForDisplay != nameTextField.stringValue else { + let newName = nameTextField?.stringValue, + feed.nameForDisplay != newName else { return } - account.renameFeed(feed, to: nameTextField.stringValue) { [weak self] result in - if case .failure(let error) = result { - self?.presentError(error) - } else { - self?.windowTitle = feed.nameForDisplay + Task { @MainActor in + + do { + try await account.renameFeed(feed, to: newName) + self.windowTitle = feed.nameForDisplay + } catch { + self.presentError(error) } } } - } diff --git a/Mac/Inspector/FolderInspectorViewController.swift b/Mac/Inspector/FolderInspectorViewController.swift index 32f463c24..dc01c7b5f 100644 --- a/Mac/Inspector/FolderInspectorViewController.swift +++ b/Mac/Inspector/FolderInspectorViewController.swift @@ -107,18 +107,20 @@ private extension FolderInspectorViewController { func renameFolderIfNecessary() { guard let folder = folder, let account = folder.account, - let nameTextField = nameTextField, - folder.nameForDisplay != nameTextField.stringValue else { + let newName = nameTextField?.stringValue, + !newName.isEmpty, + folder.nameForDisplay != newName else { return } - account.renameFolder(folder, to: nameTextField.stringValue) { [weak self] result in - if case .failure(let error) = result { - self?.presentError(error) - } else { - self?.windowTitle = folder.nameForDisplay + Task { @MainActor in + + do { + try await account.renameFolder(folder, to: newName) + self.windowTitle = folder.nameForDisplay + } catch { + self.presentError(error) } } } - } diff --git a/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift b/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift index 91f12f0e4..c239419d7 100644 --- a/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift +++ b/Mac/MainWindow/Sidebar/Renaming/RenameWindowController.swift @@ -10,7 +10,7 @@ import AppKit protocol RenameWindowControllerDelegate { - @MainActor func renameWindowController(_ windowController: RenameWindowController, didRenameObject: Any, withNewName: String) + @MainActor func renameWindowController(_ windowController: RenameWindowController, didRenameObject: Any, withNewName: String) async } final class RenameWindowController: NSWindowController { @@ -54,7 +54,11 @@ final class RenameWindowController: NSWindowController { guard let representedObject = representedObject else { return } - delegate?.renameWindowController(self, didRenameObject: representedObject, withNewName: newTitleTextField.stringValue) + + Task { @MainActor in + await delegate?.renameWindowController(self, didRenameObject: representedObject, withNewName: newTitleTextField.stringValue) + } + window?.sheetParent?.endSheet(window!, returnCode: .OK) } diff --git a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift index ee4bf4b81..bb5484ba7 100644 --- a/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift +++ b/Mac/MainWindow/Sidebar/SidebarViewController+ContextualMenus.swift @@ -171,26 +171,16 @@ extension SidebarViewController { extension SidebarViewController: RenameWindowControllerDelegate { - func renameWindowController(_ windowController: RenameWindowController, didRenameObject object: Any, withNewName name: String) { + func renameWindowController(_ windowController: RenameWindowController, didRenameObject object: Any, withNewName name: String) async { - if let feed = object as? Feed { - feed.rename(to: name) { result in - switch result { - case .success: - break - case .failure(let error): - NSApplication.shared.presentError(error) - } - } - } else if let folder = object as? Folder { - folder.rename(to: name) { result in - switch result { - case .success: - break - case .failure(let error): - NSApplication.shared.presentError(error) - } - } + guard let renamableItem = object as? Renamable else { + return + } + + do { + try await renamableItem.rename(to: name) + } catch { + NSApplication.shared.presentError(error) } } } diff --git a/iOS/Feeds/SidebarViewController.swift b/iOS/Feeds/SidebarViewController.swift index 6d8a24889..9d30add19 100644 --- a/iOS/Feeds/SidebarViewController.swift +++ b/iOS/Feeds/SidebarViewController.swift @@ -1139,59 +1139,51 @@ private extension SidebarViewController { func rename(indexPath: IndexPath) { - guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? SidebarItem else { return } + + guard let sidebarItem = coordinator.nodeFor(indexPath)?.representedObject as? SidebarItem else { + return + } let formatString = NSLocalizedString("Rename “%@”", comment: "Rename feed") - let title = NSString.localizedStringWithFormat(formatString as NSString, feed.nameForDisplay) as String - + let title = NSString.localizedStringWithFormat(formatString as NSString, sidebarItem.nameForDisplay) as String + let alertController = UIAlertController(title: title, message: nil, preferredStyle: .alert) - + let cancelTitle = NSLocalizedString("Cancel", comment: "Cancel") alertController.addAction(UIAlertAction(title: cancelTitle, style: .cancel)) - + let renameTitle = NSLocalizedString("Rename", comment: "Rename") let renameAction = UIAlertAction(title: renameTitle, style: .default) { [weak self] action in - + guard let name = alertController.textFields?[0].text, !name.isEmpty else { return } - - if let feed = feed as? Feed { - feed.rename(to: name) { result in - switch result { - case .success: - break - case .failure(let error): - self?.presentError(error) - } - } - } else if let folder = feed as? Folder { - folder.rename(to: name) { result in - switch result { - case .success: - break - case .failure(let error): + + Task { @MainActor in + + if let renamableItem = sidebarItem as? Renamable { + + do { + try await renamableItem.rename(to: name) + } catch { self?.presentError(error) } } } - } - + alertController.addAction(renameAction) alertController.preferredAction = renameAction - + alertController.addTextField() { textField in - textField.text = feed.nameForDisplay + textField.text = sidebarItem.nameForDisplay textField.placeholder = NSLocalizedString("Name", comment: "Name") } - + self.present(alertController, animated: true) { - } - } - + func delete(indexPath: IndexPath) { guard let feed = coordinator.nodeFor(indexPath)?.representedObject as? SidebarItem else { return } diff --git a/iOS/Inspector/FeedInspectorViewController.swift b/iOS/Inspector/FeedInspectorViewController.swift index 28496950e..ff8067c96 100644 --- a/iOS/Inspector/FeedInspectorViewController.swift +++ b/iOS/Inspector/FeedInspectorViewController.swift @@ -62,7 +62,10 @@ class FeedInspectorViewController: UITableViewController { if nameTextField.text != feed.nameForDisplay { let nameText = nameTextField.text ?? "" let newName = nameText.isEmpty ? (feed.name ?? NSLocalizedString("Untitled", comment: "Feed name")) : nameText - feed.rename(to: newName) { _ in } + + Task { @MainActor in + try? await feed.rename(to: newName) + } } }