From 91b0e7158a746a187025fbddce0b3a06b01f0cc1 Mon Sep 17 00:00:00 2001 From: Brent Simmons Date: Tue, 26 Mar 2024 21:10:05 -0700 Subject: [PATCH] Convert importOPML to async await. --- Account/Sources/Account/Account.swift | 30 +++++------- Account/Sources/Account/AccountDelegate.swift | 2 +- .../CloudKit/CloudKitAccountDelegate.swift | 16 +++++- .../Feedbin/FeedbinAccountDelegate.swift | 16 +++++- .../Feedly/FeedlyAccountDelegate.swift | 16 +++++- .../LocalAccount/LocalAccountDelegate.swift | 49 ++++--------------- .../NewsBlur/NewsBlurAccountDelegate.swift | 3 +- .../ReaderAPI/ReaderAPIAccountDelegate.swift | 2 +- .../NNW3/NNW3ImportController.swift | 12 ++--- .../OPML/ImportOPMLWindowController.swift | 21 ++++---- Shared/Importers/DefaultFeedsImporter.swift | 12 +++-- iOS/Settings/SettingsViewController.swift | 12 ++--- 12 files changed, 99 insertions(+), 92 deletions(-) diff --git a/Account/Sources/Account/Account.swift b/Account/Sources/Account/Account.swift index 6c306df6d..edf871913 100644 --- a/Account/Sources/Account/Account.swift +++ b/Account/Sources/Account/Account.swift @@ -428,28 +428,22 @@ public enum FetchType { try await delegate.syncArticleStatus(for: self) } - public func importOPML(_ opmlFile: URL, completion: @escaping (Result) -> Void) { + public func importOPML(_ opmlFile: URL) async throws { + guard !delegate.isOPMLImportInProgress else { - completion(.failure(AccountError.opmlImportInProgress)) - return + throw AccountError.opmlImportInProgress } - - delegate.importOPML(for: self, opmlFile: opmlFile) { [weak self] result in - switch result { - case .success: - guard let self = self else { return } - // Reset the last fetch date to get the article history for the added feeds. - self.metadata.lastArticleFetchStartTime = nil - Task { @MainActor in - try? await self.refreshAll() - } - case .failure(let error): - completion(.failure(error)) - } + + try await delegate.importOPML(for: self, opmlFile: opmlFile) + + // Reset the last fetch date to get the article history for the added feeds. + metadata.lastArticleFetchStartTime = nil + + Task { @MainActor in + try? await self.refreshAll() } - } - + public func suspendNetwork() { delegate.suspendNetwork() } diff --git a/Account/Sources/Account/AccountDelegate.swift b/Account/Sources/Account/AccountDelegate.swift index e18be7ede..2c6d8001f 100644 --- a/Account/Sources/Account/AccountDelegate.swift +++ b/Account/Sources/Account/AccountDelegate.swift @@ -30,7 +30,7 @@ import Secrets func sendArticleStatus(for account: Account) async throws func refreshArticleStatus(for account: Account) async throws - func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) + func importOPML(for account:Account, opmlFile: URL) async throws func createFolder(for account: Account, name: String, completion: @escaping (Result) -> Void) func renameFolder(for account: Account, with folder: Folder, to name: String, completion: @escaping (Result) -> Void) diff --git a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift index 1a20a648b..20538439b 100644 --- a/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift +++ b/Account/Sources/Account/CloudKit/CloudKitAccountDelegate.swift @@ -180,7 +180,21 @@ enum CloudKitAccountDelegateError: LocalizedError { } } - func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { + func importOPML(for account: Account, opmlFile: URL) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.importOPML(for: account, opmlFile: opmlFile) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { guard refreshProgress.isComplete else { completion(.success(())) return diff --git a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift index 87611430a..bc92e5d2a 100644 --- a/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift +++ b/Account/Sources/Account/Feedbin/FeedbinAccountDelegate.swift @@ -279,7 +279,21 @@ final class FeedbinAccountDelegate: AccountDelegate { } - func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { + func importOPML(for account: Account, opmlFile: URL) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.importOPML(for: account, opmlFile: opmlFile) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { var fileData: Data? diff --git a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift index 2edddcbf2..4874626cf 100644 --- a/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift +++ b/Account/Sources/Account/Feedly/FeedlyAccountDelegate.swift @@ -255,7 +255,21 @@ final class FeedlyAccountDelegate: AccountDelegate { operationQueue.addOperations([ingestUnread, ingestStarred]) } - func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result) -> Void) { + func importOPML(for account: Account, opmlFile: URL) async throws { + + try await withCheckedThrowingContinuation { continuation in + self.importOPML(for: account, opmlFile: opmlFile) { result in + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } + } + } + } + + private func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result) -> Void) { let data: Data do { diff --git a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift index 56838d7f0..06cca7530 100644 --- a/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift +++ b/Account/Sources/Account/LocalAccount/LocalAccountDelegate.swift @@ -67,50 +67,21 @@ final class LocalAccountDelegate: AccountDelegate { func refreshArticleStatus(for account: Account) async throws { } - func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { + func importOPML(for account:Account, opmlFile: URL) async throws { - Task { @MainActor in - var fileData: Data? + let opmlData = try Data(contentsOf: opmlFile) + let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) - do { - fileData = try Data(contentsOf: opmlFile) - } catch { - completion(.failure(error)) - return - } + let opmlDocument = try RSOPMLParser.parseOPML(with: parserData) + guard let children = opmlDocument.children else { + return + } - guard let opmlData = fileData else { - completion(.success(())) - return - } - - let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData) - var opmlDocument: RSOPMLDocument? - - do { - opmlDocument = try RSOPMLParser.parseOPML(with: parserData) - } catch { - completion(.failure(error)) - return - } - - guard let loadDocument = opmlDocument else { - completion(.success(())) - return - } - - guard let children = loadDocument.children else { - return - } - - BatchUpdate.shared.perform { - account.loadOPMLItems(children) - } - - completion(.success(())) + BatchUpdate.shared.perform { + account.loadOPMLItems(children) } } - + 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 6dd0d8324..77c76236d 100644 --- a/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Account/Sources/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -393,8 +393,7 @@ final class NewsBlurAccountDelegate: AccountDelegate { } } - func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result) -> ()) { - completion(.success(())) + func importOPML(for account: Account, opmlFile: URL) async throws { } func createFolder(for account: Account, name: String, completion: @escaping (Result) -> ()) { diff --git a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift index 1bbcc5d26..30297239c 100644 --- a/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift +++ b/Account/Sources/Account/ReaderAPI/ReaderAPIAccountDelegate.swift @@ -338,7 +338,7 @@ final class ReaderAPIAccountDelegate: AccountDelegate { } } - func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result) -> Void) { + func importOPML(for account:Account, opmlFile: URL) async throws { } func createFolder(for account: Account, name: String, completion: @escaping (Result) -> Void) { diff --git a/Mac/MainWindow/NNW3/NNW3ImportController.swift b/Mac/MainWindow/NNW3/NNW3ImportController.swift index 1d32b5ce1..08f03cba5 100644 --- a/Mac/MainWindow/NNW3/NNW3ImportController.swift +++ b/Mac/MainWindow/NNW3/NNW3ImportController.swift @@ -47,12 +47,12 @@ private extension NNW3ImportController { guard let opmlURL = convertToOPMLFile(subscriptionsPlistURL: subscriptionsPlistURL) else { return } - account.importOPML(opmlURL) { result in - try? FileManager.default.removeItem(at: opmlURL) - switch result { - case .success: - break - case .failure(let error): + + Task { @MainActor in + do { + try await account.importOPML(opmlURL) + try? FileManager.default.removeItem(at: opmlURL) + } catch { NSApplication.shared.presentError(error) } } diff --git a/Mac/MainWindow/OPML/ImportOPMLWindowController.swift b/Mac/MainWindow/OPML/ImportOPMLWindowController.swift index 1db6ca6d2..8530d153e 100644 --- a/Mac/MainWindow/OPML/ImportOPMLWindowController.swift +++ b/Mac/MainWindow/OPML/ImportOPMLWindowController.swift @@ -10,7 +10,7 @@ import AppKit import Account import UniformTypeIdentifiers -class ImportOPMLWindowController: NSWindowController { +final class ImportOPMLWindowController: NSWindowController { @IBOutlet weak var accountPopUpButton: NSPopUpButton! private weak var hostWindow: NSWindow? @@ -90,18 +90,17 @@ class ImportOPMLWindowController: NSWindowController { panel.allowsOtherFileTypes = false panel.beginSheetModal(for: hostWindow!) { modalResult in - if modalResult == NSApplication.ModalResponse.OK, let url = panel.url { - account.importOPML(url) { result in - switch result { - case .success: - break - case .failure(let error): - NSApplication.shared.presentError(error) - } + guard modalResult == NSApplication.ModalResponse.OK, let url = panel.url else { + return + } + + Task { @MainActor in + do { + try await account.importOPML(url) + } catch { + NSApplication.shared.presentError(error) } } } - } - } diff --git a/Shared/Importers/DefaultFeedsImporter.swift b/Shared/Importers/DefaultFeedsImporter.swift index 1df737255..6edf8f380 100644 --- a/Shared/Importers/DefaultFeedsImporter.swift +++ b/Shared/Importers/DefaultFeedsImporter.swift @@ -12,9 +12,11 @@ import Account @MainActor struct DefaultFeedsImporter { static func importDefaultFeeds(account: Account) { - let defaultFeedsURL = Bundle.main.url(forResource: "DefaultFeeds", withExtension: "opml")! - AccountManager.shared.defaultAccount.importOPML(defaultFeedsURL) { result in } - } - -} + let defaultFeedsURL = Bundle.main.url(forResource: "DefaultFeeds", withExtension: "opml")! + + Task { @MainActor in + try? await AccountManager.shared.defaultAccount.importOPML(defaultFeedsURL) + } + } +} diff --git a/iOS/Settings/SettingsViewController.swift b/iOS/Settings/SettingsViewController.swift index 4c6f4b3ae..6746045f7 100644 --- a/iOS/Settings/SettingsViewController.swift +++ b/iOS/Settings/SettingsViewController.swift @@ -349,12 +349,13 @@ class SettingsViewController: UITableViewController { extension SettingsViewController: UIDocumentPickerDelegate { func documentPicker(_ controller: UIDocumentPickerViewController, didPickDocumentsAt urls: [URL]) { + for url in urls { - opmlAccount?.importOPML(url) { result in - switch result { - case .success: - break - case .failure: + + Task { @MainActor in + do { + try await opmlAccount?.importOPML(url) + } catch { let title = NSLocalizedString("Import Failed", comment: "Import Failed") let message = NSLocalizedString("We were unable to process the selected file. Please ensure that it is a properly formatted OPML file.", comment: "Import Failed Message") self.presentError(title: title, message: message) @@ -362,7 +363,6 @@ extension SettingsViewController: UIDocumentPickerDelegate { } } } - } // MARK: Private