Convert several methods to async await.

This commit is contained in:
Brent Simmons 2024-04-07 23:43:00 -07:00
parent 2dc9b8586c
commit 4ad43b5b9a
2 changed files with 136 additions and 290 deletions

View File

@ -82,28 +82,23 @@ final class FeedbinAccountDelegate: AccountDelegate {
}
func refreshAll(for account: Account) async throws {
refreshProgress.addToNumberOfTasksAndRemaining(5)
do {
try await refreshAccount(account)
} catch {
refreshProgress.clear()
let wrappedError = AccountError.wrappedError(error: error, account: account)
throw wrappedError
}
try await withCheckedThrowingContinuation { continuation in
refreshAccount(account) { result in
self.refreshArticlesAndStatuses(account) { result in
switch result {
case .success():
self.refreshArticlesAndStatuses(account) { result in
switch result {
case .success():
continuation.resume()
case .failure(let error):
DispatchQueue.main.async {
self.refreshProgress.clear()
let wrappedError = AccountError.wrappedError(error: error, account: account)
continuation.resume(throwing: wrappedError)
}
}
}
continuation.resume()
case .failure(let error):
DispatchQueue.main.async {
self.refreshProgress.clear()
@ -253,7 +248,6 @@ final class FeedbinAccountDelegate: AccountDelegate {
os_log(.info, log: self.log, "Retrieving unread entries failed: %@.", error.localizedDescription)
group.leave()
}
}
group.enter()
@ -268,7 +262,6 @@ final class FeedbinAccountDelegate: AccountDelegate {
os_log(.info, log: self.log, "Retrieving starred entries failed: %@.", error.localizedDescription)
group.leave()
}
}
group.notify(queue: DispatchQueue.main) {
@ -279,67 +272,43 @@ final class FeedbinAccountDelegate: AccountDelegate {
completion(.success(()))
}
}
}
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, Error>) -> Void) {
var fileData: Data?
do {
fileData = try Data(contentsOf: opmlFile)
} catch {
completion(.failure(error))
let opmlData = try Data(contentsOf: opmlFile)
if opmlData.isEmpty {
return
}
guard let opmlData = fileData else {
completion(.success(()))
return
}
os_log(.debug, log: log, "Begin importing OPML...")
isOPMLImportInProgress = true
refreshProgress.addToNumberOfTasksAndRemaining(1)
caller.importOPML(opmlData: opmlData) { result in
switch result {
case .success(let importResult):
if importResult.complete {
os_log(.debug, log: self.log, "Import OPML done.")
self.refreshProgress.completeTask()
self.isOPMLImportInProgress = false
DispatchQueue.main.async {
completion(.success(()))
}
} else {
self.checkImportResult(opmlImportResultID: importResult.importResultID, completion: completion)
}
case .failure(let error):
os_log(.debug, log: self.log, "Import OPML failed.")
self.refreshProgress.completeTask()
self.isOPMLImportInProgress = false
DispatchQueue.main.async {
let wrappedError = AccountError.wrappedError(error: error, account: account)
completion(.failure(wrappedError))
}
do {
let importResult = try await caller.importOPML(opmlData: opmlData)
if importResult.complete {
os_log(.debug, log: self.log, "Import OPML done.")
refreshProgress.completeTask()
isOPMLImportInProgress = false
} else {
try await checkImportResult(opmlImportResultID: importResult.importResultID)
refreshProgress.completeTask()
isOPMLImportInProgress = false
}
} catch {
os_log(.debug, log: self.log, "Import OPML failed.")
refreshProgress.completeTask()
isOPMLImportInProgress = false
let wrappedError = AccountError.wrappedError(error: error, account: account)
throw wrappedError
}
}
func createFolder(for account: Account, name: String) async throws -> Folder {
@ -787,29 +756,9 @@ final class FeedbinAccountDelegate: AccountDelegate {
static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL?, secretsProvider: SecretsProvider) async throws -> Credentials? {
try await withCheckedThrowingContinuation { continuation in
self.validateCredentials(transport: transport, credentials: credentials, endpoint: endpoint, secretsProvider: secretsProvider) { result in
switch result {
case .success(let credentials):
continuation.resume(returning: credentials)
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
private static func validateCredentials(transport: Transport, credentials: Credentials, endpoint: URL? = nil, secretsProvider: SecretsProvider, completion: @escaping (Result<Credentials?, Error>) -> Void) {
let caller = FeedbinAPICaller(transport: transport)
caller.credentials = credentials
caller.validateCredentials() { result in
DispatchQueue.main.async {
completion(result)
}
}
return try await caller.validateCredentials()
}
// MARK: Suspend and Resume (for iOS)
@ -841,89 +790,67 @@ final class FeedbinAccountDelegate: AccountDelegate {
private extension FeedbinAccountDelegate {
func checkImportResult(opmlImportResultID: Int) async throws {
try await withCheckedThrowingContinuation { continuation in
self.checkImportResult(opmlImportResultID: opmlImportResultID) { result in
switch result {
case .success:
continuation.resume()
case .failure(let error):
continuation.resume(throwing: error)
}
}
}
}
func checkImportResult(opmlImportResultID: Int, completion: @escaping (Result<Void, Error>) -> Void) {
DispatchQueue.main.async {
Timer.scheduledTimer(withTimeInterval: 15, repeats: true) { timer in
os_log(.debug, log: self.log, "Checking status of OPML import...")
self.caller.retrieveOPMLImportResult(importID: opmlImportResultID) { result in
switch result {
case .success(let importResult):
if let result = importResult, result.complete {
Task { @MainActor in
os_log(.debug, log: self.log, "Checking status of OPML import...")
do {
let importResult = try await self.caller.retrieveOPMLImportResult(importID: opmlImportResultID)
if let importResult, importResult.complete {
os_log(.debug, log: self.log, "Checking status of OPML import successfully completed.")
timer.invalidate()
self.refreshProgress.completeTask()
self.isOPMLImportInProgress = false
DispatchQueue.main.async {
completion(.success(()))
}
completion(.success(()))
}
case .failure(let error):
} catch {
os_log(.debug, log: self.log, "Import OPML check failed.")
timer.invalidate()
self.refreshProgress.completeTask()
self.isOPMLImportInProgress = false
DispatchQueue.main.async {
completion(.failure(error))
}
}
}
}
}
}
func refreshAccount(_ account: Account, completion: @escaping (Result<Void, Error>) -> Void) {
caller.retrieveTags { result in
switch result {
case .success(let tags):
self.refreshProgress.completeTask()
self.caller.retrieveSubscriptions { result in
switch result {
case .success(let subscriptions):
self.refreshProgress.completeTask()
self.forceExpireFolderFeedRelationship(account, tags)
self.caller.retrieveTaggings { result in
MainActor.assumeIsolated {
switch result {
case .success(let taggings):
BatchUpdate.shared.perform {
self.syncFolders(account, tags)
self.syncFeeds(account, subscriptions)
self.syncFeedFolderRelationship(account, taggings)
}
self.refreshProgress.completeTask()
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
}
}
case .failure(let error):
completion(.failure(error))
}
}
case .failure(let error):
completion(.failure(error))
}
}
}
func refreshAccount(_ account: Account) async throws {
let tags = try await caller.retrieveTags()
refreshProgress.completeTask()
let subscriptions = try await caller.retrieveSubscriptions()
refreshProgress.completeTask()
forceExpireFolderFeedRelationship(account, tags)
let taggings = try await caller.retrieveTaggings()
BatchUpdate.shared.perform {
self.syncFolders(account, tags)
self.syncFeeds(account, subscriptions)
self.syncFeedFolderRelationship(account, taggings)
}
refreshProgress.completeTask()
}
func refreshArticlesAndStatuses(_ account: Account, completion: @escaping (Result<Void, Error>) -> Void) {

View File

@ -55,119 +55,61 @@ final class FeedbinAPICaller: NSObject {
suspended = false
}
func validateCredentials(completion: @escaping (Result<Credentials?, Error>) -> Void) {
func validateCredentials() async throws -> Credentials? {
let callURL = feedbinBaseURL.appendingPathComponent("authentication.json")
let request = URLRequest(url: callURL, credentials: credentials)
transport.send(request: request) { result in
if self.suspended {
completion(.failure(TransportError.suspended))
return
}
switch result {
case .success:
completion(.success(self.credentials))
case .failure(let error):
switch error {
case TransportError.httpError(let status):
if status == 401 {
completion(.success(nil))
} else {
completion(.failure(error))
}
default:
completion(.failure(error))
}
do {
try await transport.send(request: request)
return credentials
} catch {
if case TransportError.httpError(let status) = error, status == 401 {
return nil
}
throw error
}
}
func importOPML(opmlData: Data, completion: @escaping (Result<FeedbinImportResult, Error>) -> Void) {
func importOPML(opmlData: Data) async throws -> FeedbinImportResult {
let callURL = feedbinBaseURL.appendingPathComponent("imports.json")
var request = URLRequest(url: callURL, credentials: credentials)
request.addValue("text/xml; charset=utf-8", forHTTPHeaderField: HTTPRequestHeader.contentType)
transport.send(request: request, method: HTTPMethod.post, payload: opmlData) { result in
if self.suspended {
completion(.failure(TransportError.suspended))
return
}
switch result {
case .success(let (_, data)):
guard let resultData = data else {
completion(.failure(TransportError.noData))
break
}
do {
let result = try JSONDecoder().decode(FeedbinImportResult.self, from: resultData)
completion(.success(result))
} catch {
completion(.failure(error))
}
case .failure(let error):
completion(.failure(error))
}
let (_, data) = try await transport.send(request: request, method: HTTPMethod.post, payload: opmlData)
guard let data else {
throw TransportError.noData
}
let parsingTask = Task.detached { () throws -> FeedbinImportResult in
try JSONDecoder().decode(FeedbinImportResult.self, from: data)
}
let importResult = try await parsingTask.value
return importResult
}
func retrieveOPMLImportResult(importID: Int, completion: @escaping (Result<FeedbinImportResult?, Error>) -> Void) {
func retrieveOPMLImportResult(importID: Int) async throws -> FeedbinImportResult? {
let callURL = feedbinBaseURL.appendingPathComponent("imports/\(importID).json")
let request = URLRequest(url: callURL, credentials: credentials)
transport.send(request: request, resultType: FeedbinImportResult.self) { result in
if self.suspended {
completion(.failure(TransportError.suspended))
return
}
switch result {
case .success(let (_, importResult)):
completion(.success(importResult))
case .failure(let error):
completion(.failure(error))
}
}
let (_, importResult) = try await transport.send(request: request, resultType: FeedbinImportResult.self)
return importResult
}
func retrieveTags(completion: @escaping (Result<[FeedbinTag]?, Error>) -> Void) {
func retrieveTags() async throws -> [FeedbinTag]? {
let callURL = feedbinBaseURL.appendingPathComponent("tags.json")
let conditionalGet = accountMetadata?.conditionalGetInfo[ConditionalGetKeys.tags]
let request = URLRequest(url: callURL, credentials: credentials, conditionalGet: conditionalGet)
transport.send(request: request, resultType: [FeedbinTag].self) { result in
if self.suspended {
completion(.failure(TransportError.suspended))
return
}
switch result {
case .success(let (response, tags)):
self.storeConditionalGet(key: ConditionalGetKeys.tags, headers: response.allHeaderFields)
completion(.success(tags))
case .failure(let error):
completion(.failure(error))
}
}
let (response, tags) = try await transport.send(request: request, resultType: [FeedbinTag].self)
storeConditionalGet(key: ConditionalGetKeys.tags, headers: response.allHeaderFields)
return tags
}
func renameTag(oldName: String, newName: String, completion: @escaping (Result<Void, Error>) -> Void) {
@ -190,31 +132,19 @@ final class FeedbinAPICaller: NSObject {
}
}
func retrieveSubscriptions(completion: @escaping (Result<[FeedbinSubscription]?, Error>) -> Void) {
func retrieveSubscriptions() async throws -> [FeedbinSubscription]? {
var callComponents = URLComponents(url: feedbinBaseURL.appendingPathComponent("subscriptions.json"), resolvingAgainstBaseURL: false)!
callComponents.queryItems = [URLQueryItem(name: "mode", value: "extended")]
let conditionalGet = accountMetadata?.conditionalGetInfo[ConditionalGetKeys.subscriptions]
let request = URLRequest(url: callComponents.url!, credentials: credentials, conditionalGet: conditionalGet)
transport.send(request: request, resultType: [FeedbinSubscription].self) { result in
if self.suspended {
completion(.failure(TransportError.suspended))
return
}
switch result {
case .success(let (response, subscriptions)):
self.storeConditionalGet(key: ConditionalGetKeys.subscriptions, headers: response.allHeaderFields)
completion(.success(subscriptions))
case .failure(let error):
completion(.failure(error))
}
}
let (response, subscriptions) = try await transport.send(request: request, resultType: [FeedbinSubscription].self)
storeConditionalGet(key: ConditionalGetKeys.subscriptions, headers: response.allHeaderFields)
return subscriptions
}
func createSubscription(url: String, completion: @escaping (Result<CreateSubscriptionResult, Error>) -> Void) {
@ -334,30 +264,19 @@ final class FeedbinAPICaller: NSObject {
}
}
func retrieveTaggings(completion: @escaping (Result<[FeedbinTagging]?, Error>) -> Void) {
func retrieveTaggings() async throws -> [FeedbinTagging]? {
let callURL = feedbinBaseURL.appendingPathComponent("taggings.json")
let conditionalGet = accountMetadata?.conditionalGetInfo[ConditionalGetKeys.taggings]
let request = URLRequest(url: callURL, credentials: credentials, conditionalGet: conditionalGet)
transport.send(request: request, resultType: [FeedbinTagging].self) { result in
if self.suspended {
completion(.failure(TransportError.suspended))
return
}
switch result {
case .success(let (response, taggings)):
self.storeConditionalGet(key: ConditionalGetKeys.taggings, headers: response.allHeaderFields)
completion(.success(taggings))
case .failure(let error):
completion(.failure(error))
}
}
let (response, taggings) = try await transport.send(request: request, resultType: [FeedbinTagging].self)
storeConditionalGet(key: ConditionalGetKeys.taggings, headers: response.allHeaderFields)
return taggings
}
func createTagging(feedID: Int, name: String, completion: @escaping (Result<Int, Error>) -> Void) {
let callURL = feedbinBaseURL.appendingPathComponent("taggings.json")