From 5c3cbd30f7548e786ac950d7dea3b107a9cae9c1 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Fri, 14 Jul 2023 14:11:51 -0700 Subject: [PATCH] Continue adopting async/await. --- Account/Sources/Account/AccountDelegate.swift | 3 +- .../CloudKit/CloudKitAccountDelegate.swift | 21 +++ .../Feedbin/FeedbinAccountDelegate.swift | 31 ++++ .../Feedly/FeedlyAccountDelegate.swift | 33 ++++- .../LocalAccount/LocalAccountDelegate.swift | 4 + .../NewsBlur/NewsBlurAccountDelegate.swift | 27 ++++ .../ReaderAPI/ReaderAPIAccountDelegate.swift | 31 +++- .../AddFeed/AddWebFeedWindowController.swift | 136 ------------------ 8 files changed, 147 insertions(+), 139 deletions(-) delete mode 100644 Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift diff --git a/Account/Sources/Account/AccountDelegate.swift b/Account/Sources/Account/AccountDelegate.swift index 4233d4d6e..2ce4df5ec 100644 --- a/Account/Sources/Account/AccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegate.swift @@ -37,7 +37,8 @@ import Secrets 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, feed: Feed, name: String) async throws + func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) 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 1c49c62be..f814ffabf 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -184,6 +184,27 @@ final class CloudKitAccountDelegate: AccountDelegate, Logging { createRSSFeed(for: account, url: url, editedName: editedName, container: container, validateFeed: validateFeed, completion: completion) } + func renameFeed(for account: Account, feed: Feed, name: String) async throws { + let editedName = name.isEmpty ? nil : name + refreshProgress.addToNumberOfTasksAndRemaining(1) + + try await withCheckedThrowingContinuation { continuation in + accountZone.renameFeed(feed, editedName: editedName) { result in + Task { @MainActor in + self.refreshProgress.completeTask() + switch result { + case .success: + feed.editedName = name + continuation.resume() + case .failure(let error): + self.processAccountError(account, error) + continuation.resume(throwing: error) + } + } + } + } + } + func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { let editedName = name.isEmpty ? nil : name refreshProgress.addToNumberOfTasksAndRemaining(1) diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index 904af359b..85d8c0453 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -418,6 +418,37 @@ public enum FeedbinAccountDelegateError: String, Error { } + func renameFeed(for account: Account, feed: Feed, name: String) async throws { + // This error should never happen + guard let subscriptionID = feed.externalID else { + throw FeedbinAccountDelegateError.invalidParameter + } + + refreshProgress.addToNumberOfTasksAndRemaining(1) + + try await withCheckedThrowingContinuation { continuation in + + caller.renameSubscription(subscriptionID: subscriptionID, newName: name) { result in + Task { @MainActor in + + self.refreshProgress.completeTask() + switch result { + case .success: + DispatchQueue.main.async { + feed.editedName = name + continuation.resume() + } + case .failure(let error): + DispatchQueue.main.async { + let wrappedError = WrappedAccountError(account: account, underlyingError: error) + continuation.resume(throwing: wrappedError) + } + } + } + } + } + } + func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { // This error should never happen diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index 0c7c6a673..0b62c58ab 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -343,7 +343,38 @@ final class FeedlyAccountDelegate: AccountDelegate, Logging { } } } - + + func renameFeed(for account: Account, feed: Feed, name: String) async throws { + + let folderCollectionIDs = account.folders?.filter { $0.has(feed) }.compactMap { $0.externalID } + guard let collectionIDs = folderCollectionIDs, let collectionID = collectionIDs.first else { + throw FeedlyAccountDelegateError.unableToRenameFeed(feed.nameForDisplay, name) + } + + let feedID = FeedlyFeedResourceId(id: feed.feedID) + let editedNameBefore = feed.editedName + + // optimistically set the name + feed.editedName = name + + // Adding an existing feed updates it. + // Updating feed name in one folder/collection updates it for all folders/collections. + try await withCheckedThrowingContinuation { continuation in + caller.addFeed(with: feedID, title: name, toCollectionWith: collectionID) { result in + Task { @MainActor in + switch result { + case .success: + continuation.resume() + + case .failure(let error): + feed.editedName = editedNameBefore + 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/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index 9f4ee099b..fd937ad87 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -129,6 +129,10 @@ final class LocalAccountDelegate: AccountDelegate, Logging { createRSSFeed(for: account, url: url, editedName: name, container: container, completion: completion) } + func renameFeed(for account: Account, feed: Feed, name: String) async throws { + feed.editedName = name + } + func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { feed.editedName = name completion(.success(())) diff --git a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift index adde36f7b..e65c46c0e 100644 --- a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -439,6 +439,33 @@ final class NewsBlurAccountDelegate: AccountDelegate, Logging { } } + func renameFeed(for account: Account, feed: Feed, name: String) async throws { + guard let feedID = feed.externalID else { + throw NewsBlurError.invalidParameter + } + + refreshProgress.addToNumberOfTasksAndRemaining(1) + + try await withCheckedThrowingContinuation { continuation in + + caller.renameFeed(feedID: feedID, newName: name) { result in + Task { @MainActor in + self.refreshProgress.completeTask() + + switch result { + case .success: + feed.editedName = name + continuation.resume() + + case .failure(let error): + let wrappedError = WrappedAccountError(account: account, underlyingError: error) + continuation.resume(throwing: wrappedError) + } + } + } + } + } + func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> ()) { guard let feedID = feed.externalID else { completion(.failure(NewsBlurError.invalidParameter)) diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 86c8e19d4..7f6e7c306 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -436,7 +436,36 @@ public enum ReaderAPIAccountDelegateError: LocalizedError { } } - + + func renameFeed(for account: Account, feed: Feed, name: String) async throws { + // This error should never happen + guard let subscriptionID = feed.externalID else { + throw ReaderAPIAccountDelegateError.invalidParameter + } + + refreshProgress.addToNumberOfTasksAndRemaining(1) + + try await withCheckedThrowingContinuation { continuation in + caller.renameSubscription(subscriptionID: subscriptionID, newName: name) { result in + + Task { @MainActor in + self.refreshProgress.completeTask() + switch result { + case .success: + DispatchQueue.main.async { + feed.editedName = name + continuation.resume() + } + case .failure(let error): + let wrappedError = WrappedAccountError(account: account, underlyingError: error) + continuation.resume(throwing: wrappedError) + } + } + } + } + } + + func renameFeed(for account: Account, with feed: Feed, to name: String, completion: @escaping (Result) -> Void) { // This error should never happen diff --git a/Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift b/Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift deleted file mode 100644 index fb9a3c82c..000000000 --- a/Mac/MainWindow/AddFeed/AddWebFeedWindowController.swift +++ /dev/null @@ -1,136 +0,0 @@ -// -// AddFeedWindowController.swift -// NetNewsWire -// -// Created by Brent Simmons on 8/1/15. -// Copyright © 2015 Ranchero Software, LLC. All rights reserved. -// - -import AppKit -import RSCore -import RSTree -import Articles -import Account - -@MainActor final class AddFeedWindowController : NSWindowController, AddFeedWindowControllerProtocol { - - @IBOutlet var urlTextField: NSTextField! - @IBOutlet var nameTextField: NSTextField! - @IBOutlet var addButton: NSButton! - @IBOutlet var folderPopupButton: NSPopUpButton! - - private var urlString: String? - private var initialName: String? - private weak var initialAccount: Account? - private var initialFolder: Folder? - private weak var delegate: AddFeedWindowControllerDelegate? - private var folderTreeController: TreeController! - - private var userEnteredTitle: String? { - var s = nameTextField.stringValue - s = s.collapsingWhitespace - if s.isEmpty { - return nil - } - return s - } - - var hostWindow: NSWindow! - - convenience init(urlString: String?, name: String?, account: Account?, folder: Folder?, folderTreeController: TreeController, delegate: AddFeedWindowControllerDelegate?) { - self.init(windowNibName: NSNib.Name("AddFeedSheet")) - self.urlString = urlString - self.initialName = name - self.initialAccount = account - self.initialFolder = folder - self.delegate = delegate - self.folderTreeController = folderTreeController - } - - func runSheetOnWindow(_ hostWindow: NSWindow) { - hostWindow.beginSheet(window!) { (returnCode: NSApplication.ModalResponse) -> Void in - } - } - - override func windowDidLoad() { - if let urlString = urlString { - urlTextField.stringValue = urlString - } - if let initialName = initialName, !initialName.isEmpty { - nameTextField.stringValue = initialName - } - - folderPopupButton.menu = FolderTreeMenu.createFolderPopupMenu(with: folderTreeController.rootNode) - - if let account = initialAccount { - FolderTreeMenu.select(account: account, folder: initialFolder, in: folderPopupButton) - } else if let container = AddFeedDefaultContainer.defaultContainer { - if let folder = container as? Folder, let account = folder.account { - FolderTreeMenu.select(account: account, folder: folder, in: folderPopupButton) - } else { - if let account = container as? Account { - FolderTreeMenu.select(account: account, folder: nil, in: folderPopupButton) - } - } - } - - updateUI() - } - - // MARK: Actions - - @IBAction func cancel(_ sender: Any?) { - cancelSheet() - } - - @IBAction func addFeed(_ sender: Any?) { - let urlString = urlTextField.stringValue - let normalizedURLString = urlString.normalizedURL - - if normalizedURLString.isEmpty { - cancelSheet() - return; - } - guard let url = URL(unicodeString: normalizedURLString) else { - cancelSheet() - return - } - - guard let container = selectedContainer() else { return } - AddFeedDefaultContainer.saveDefaultContainer(container) - - delegate?.addFeedWindowController(self, userEnteredURL: url, userEnteredTitle: userEnteredTitle, container: container) - - } - - @IBAction func localShowFeedList(_ sender: Any?) { - NSApplication.shared.sendAction(NSSelectorFromString("showFeedList:"), to: nil, from: sender) - hostWindow.endSheet(window!, returnCode: NSApplication.ModalResponse.continue) - } - - // MARK: NSTextFieldDelegate - - @objc func controlTextDidEndEditing(_ obj: Notification) { - updateUI() - } - - @objc func controlTextDidChange(_ obj: Notification) { - updateUI() - } -} - -private extension AddFeedWindowController { - - private func updateUI() { - addButton.isEnabled = urlTextField.stringValue.mayBeURL && selectedContainer() != nil - } - - func cancelSheet() { - delegate?.addFeedWindowControllerUserDidCancel(self) - } - - func selectedContainer() -> Container? { - guard folderPopupButton.selectedItem?.isEnabled ?? false else { return nil } - return folderPopupButton.selectedItem?.representedObject as? Container - } -}