Convert methods to async await.

This commit is contained in:
Brent Simmons 2024-04-23 20:48:06 -07:00
parent a56ffa9405
commit c2197ac854
7 changed files with 133 additions and 239 deletions

View File

@ -422,19 +422,10 @@ extension FeedlyAPICaller: FeedlyGetStreamContentsService {
extension FeedlyAPICaller: FeedlyGetStreamIDsService {
@MainActor func getStreamIDs(for resource: FeedlyResourceID, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIDs, Error>) -> ()) {
guard !isSuspended else {
return DispatchQueue.main.async {
completion(.failure(TransportError.suspended))
}
}
guard let accessToken = credentials?.secret else {
return DispatchQueue.main.async {
completion(.failure(CredentialsError.incompleteCredentials))
}
}
@MainActor func getStreamIDs(for resource: FeedlyResourceID, continuation: String? = nil, newerThan: Date?, unreadOnly: Bool?) async throws -> FeedlyStreamIDs {
guard !isSuspended else { throw TransportError.suspended }
var components = baseURLComponents
components.path = "/v3/streams/ids"
@ -469,22 +460,16 @@ extension FeedlyAPICaller: FeedlyGetStreamIDsService {
}
var request = URLRequest(url: url)
request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType)
request.addValue("application/json", forHTTPHeaderField: "Accept-Type")
request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization)
send(request: request, resultType: FeedlyStreamIDs.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in
switch result {
case .success(let (_, collections)):
if let response = collections {
completion(.success(response))
} else {
completion(.failure(URLError(.cannotDecodeContentData)))
}
case .failure(let error):
completion(.failure(error))
}
addJSONHeaders(&request)
try addOAuthAccessToken(&request)
let (_, collections) = try await send(request: request, resultType: FeedlyStreamIDs.self)
guard let collections else {
throw URLError(.cannotDecodeContentData)
}
return collections
}
}

View File

@ -45,101 +45,64 @@ final class FeedlyIngestStarredArticleIDsOperation: FeedlyOperation {
}
private func getStreamIDs(_ continuation: String?) {
service.getStreamIDs(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil, completion: didGetStreamIDs(_:))
}
private func didGetStreamIDs(_ result: Result<FeedlyStreamIDs, Error>) {
guard !isCanceled else {
didFinish()
return
}
switch result {
case .success(let streamIDs):
remoteEntryIDs.formUnion(streamIDs.ids)
guard let continuation = streamIDs.continuation else {
removeEntryIDsWithPendingStatus()
return
Task { @MainActor in
do {
let streamIDs = try await service.getStreamIDs(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil)
remoteEntryIDs.formUnion(streamIDs.ids)
guard let continuation = streamIDs.continuation else {
try await removeEntryIDsWithPendingStatus()
didFinish()
return
}
getStreamIDs(continuation)
} catch {
didFinish(with: error)
}
getStreamIDs(continuation)
case .failure(let error):
didFinish(with: error)
}
}
/// Do not override pending statuses with the remote statuses of the same articles, otherwise an article will temporarily re-acquire the remote status before the pending status is pushed and subseqently pulled.
private func removeEntryIDsWithPendingStatus() {
guard !isCanceled else {
didFinish()
return
}
Task { @MainActor in
private func removeEntryIDsWithPendingStatus() async throws {
do {
if let pendingArticleIDs = try await self.database.selectPendingStarredStatusArticleIDs() {
self.remoteEntryIDs.subtract(pendingArticleIDs)
}
self.updateStarredStatuses()
} catch {
self.didFinish(with: error)
}
if let pendingArticleIDs = try await database.selectPendingStarredStatusArticleIDs() {
remoteEntryIDs.subtract(pendingArticleIDs)
}
try await updateStarredStatuses()
}
private func updateStarredStatuses() {
guard !isCanceled else {
didFinish()
return
}
Task { @MainActor in
private func updateStarredStatuses() async throws {
do {
if let localStarredArticleIDs = try await account.fetchStarredArticleIDs() {
self.processStarredArticleIDs(localStarredArticleIDs)
}
} catch {
self.didFinish(with: error)
}
if let localStarredArticleIDs = try await account.fetchStarredArticleIDs() {
try await processStarredArticleIDs(localStarredArticleIDs)
}
}
func processStarredArticleIDs(_ localStarredArticleIDs: Set<String>) {
func processStarredArticleIDs(_ localStarredArticleIDs: Set<String>) async throws {
guard !isCanceled else {
didFinish()
return
var markAsStarredError: Error?
var markAsUnstarredError: Error?
let remoteStarredArticleIDs = remoteEntryIDs
do {
try await account.markAsStarred(remoteStarredArticleIDs)
} catch {
markAsStarredError = error
}
Task { @MainActor in
let deltaUnstarredArticleIDs = localStarredArticleIDs.subtracting(remoteStarredArticleIDs)
do {
try await account.markAsUnstarred(deltaUnstarredArticleIDs)
} catch {
markAsUnstarredError = error
}
var markAsStarredError: Error?
var markAsUnstarredError: Error?
let remoteStarredArticleIDs = remoteEntryIDs
do {
try await account.markAsStarred(remoteStarredArticleIDs)
} catch {
markAsStarredError = error
}
let deltaUnstarredArticleIDs = localStarredArticleIDs.subtracting(remoteStarredArticleIDs)
do {
try await account.markAsUnstarred(deltaUnstarredArticleIDs)
} catch {
markAsUnstarredError = error
}
if let markingError = markAsStarredError ?? markAsUnstarredError {
self.didFinish(with: markingError)
}
self.didFinish()
if let markingError = markAsStarredError ?? markAsUnstarredError {
throw markingError
}
}
}

View File

@ -41,36 +41,24 @@ class FeedlyIngestStreamArticleIDsOperation: FeedlyOperation {
}
private func getStreamIDs(_ continuation: String?) {
service.getStreamIDs(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil, completion: didGetStreamIDs(_:))
}
private func didGetStreamIDs(_ result: Result<FeedlyStreamIDs, Error>) {
guard !isCanceled else {
didFinish()
return
}
switch result {
case .success(let streamIDs):
Task { @MainActor in
Task { @MainActor in
do {
try await account.createStatusesIfNeeded(articleIDs: Set(streamIDs.ids))
do {
let streamIDs = try await service.getStreamIDs(for: resource, continuation: continuation, newerThan: nil, unreadOnly: nil)
guard let continuation = streamIDs.continuation else {
os_log(.debug, log: self.log, "Reached end of stream for %@", self.resource.id)
self.didFinish()
return
}
try await account.createStatusesIfNeeded(articleIDs: Set(streamIDs.ids))
self.getStreamIDs(continuation)
} catch {
self.didFinish(with: error)
guard let continuation = streamIDs.continuation else {
os_log(.debug, log: self.log, "Reached end of stream for %@", self.resource.id)
self.didFinish()
return
}
self.getStreamIDs(continuation)
} catch {
didFinish(with: error)
}
case .failure(let error):
didFinish(with: error)
}
}
}

View File

@ -46,102 +46,65 @@ final class FeedlyIngestUnreadArticleIDsOperation: FeedlyOperation {
}
private func getStreamIDs(_ continuation: String?) {
service.getStreamIDs(for: resource, continuation: continuation, newerThan: nil, unreadOnly: true, completion: didGetStreamIDs(_:))
}
private func didGetStreamIDs(_ result: Result<FeedlyStreamIDs, Error>) {
guard !isCanceled else {
didFinish()
return
}
switch result {
case .success(let streamIDs):
remoteEntryIDs.formUnion(streamIDs.ids)
guard let continuation = streamIDs.continuation else {
removeEntryIDsWithPendingStatus()
return
Task { @MainActor in
do {
let streamIDs = try await service.getStreamIDs(for: resource, continuation: continuation, newerThan: nil, unreadOnly: true)
remoteEntryIDs.formUnion(streamIDs.ids)
guard let continuation = streamIDs.continuation else {
try await removeEntryIDsWithPendingStatus()
didFinish()
return
}
getStreamIDs(continuation)
} catch {
didFinish(with: error)
}
getStreamIDs(continuation)
case .failure(let error):
didFinish(with: error)
}
}
/// Do not override pending statuses with the remote statuses of the same articles, otherwise an article will temporarily re-acquire the remote status before the pending status is pushed and subseqently pulled.
private func removeEntryIDsWithPendingStatus() {
guard !isCanceled else {
didFinish()
return
}
Task { @MainActor in
private func removeEntryIDsWithPendingStatus() async throws {
do {
if let pendingArticleIDs = try await self.database.selectPendingReadStatusArticleIDs() {
self.remoteEntryIDs.subtract(pendingArticleIDs)
}
self.updateUnreadStatuses()
} catch {
self.didFinish(with: error)
}
if let pendingArticleIDs = try await database.selectPendingReadStatusArticleIDs() {
remoteEntryIDs.subtract(pendingArticleIDs)
}
try await updateUnreadStatuses()
}
private func updateUnreadStatuses() {
guard !isCanceled else {
didFinish()
return
}
Task { @MainActor in
private func updateUnreadStatuses() async throws {
do {
if let localUnreadArticleIDs = try await account.fetchUnreadArticleIDs() {
self.processUnreadArticleIDs(localUnreadArticleIDs)
}
} catch {
self.didFinish(with: error)
}
if let localUnreadArticleIDs = try await account.fetchUnreadArticleIDs() {
try await processUnreadArticleIDs(localUnreadArticleIDs)
}
}
private func processUnreadArticleIDs(_ localUnreadArticleIDs: Set<String>) {
guard !isCanceled else {
didFinish()
return
}
private func processUnreadArticleIDs(_ localUnreadArticleIDs: Set<String>) async throws {
let remoteUnreadArticleIDs = remoteEntryIDs
Task { @MainActor in
var markAsUnreadError: Error?
var markAsReadError: Error?
var markAsUnreadError: Error?
var markAsReadError: Error?
do {
try await account.markAsUnread(remoteUnreadArticleIDs)
} catch {
markAsUnreadError = error
}
do {
try await account.markAsUnread(remoteUnreadArticleIDs)
} catch {
markAsUnreadError = error
}
let articleIDsToMarkRead = localUnreadArticleIDs.subtracting(remoteUnreadArticleIDs)
do {
try await account.markAsRead(articleIDsToMarkRead)
} catch {
markAsReadError = error
}
let articleIDsToMarkRead = localUnreadArticleIDs.subtracting(remoteUnreadArticleIDs)
do {
try await account.markAsRead(articleIDsToMarkRead)
} catch {
markAsReadError = error
}
if let markingError = markAsReadError ?? markAsUnreadError {
self.didFinish(with: markingError)
}
self.didFinish()
if let markingError = markAsReadError ?? markAsUnreadError {
throw markingError
}
}
}

View File

@ -10,6 +10,7 @@ import Foundation
import os.log
public protocol FeedlyGetStreamIDsOperationDelegate: AnyObject {
func feedlyGetStreamIDsOperation(_ operation: FeedlyGetStreamIDsOperation, didGet streamIDs: FeedlyStreamIDs)
}
@ -46,16 +47,15 @@ public final class FeedlyGetStreamIDsOperation: FeedlyOperation, FeedlyEntryIden
weak var streamIDsDelegate: FeedlyGetStreamIDsOperationDelegate?
public override func run() {
service.getStreamIDs(for: resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly) { result in
switch result {
case .success(let stream):
Task { @MainActor in
do {
let stream = try await service.getStreamIDs(for: resource, continuation: continuation, newerThan: newerThan, unreadOnly: unreadOnly)
self.streamIDs = stream
self.streamIDsDelegate?.feedlyGetStreamIDsOperation(self, didGet: stream)
self.didFinish()
case .failure(let error):
} catch {
os_log(.debug, log: self.log, "Unable to get stream ids: %{public}@.", error as NSError)
self.didFinish(with: error)
}

View File

@ -45,35 +45,29 @@ public final class FeedlyGetUpdatedArticleIDsOperation: FeedlyOperation, FeedlyE
}
private func getStreamIDs(_ continuation: String?) {
guard let date = newerThan else {
os_log(.debug, log: log, "No date provided so everything must be new (nothing is updated).")
didFinish()
return
}
service.getStreamIDs(for: resource, continuation: continuation, newerThan: date, unreadOnly: nil, completion: didGetStreamIDs(_:))
}
private func didGetStreamIDs(_ result: Result<FeedlyStreamIDs, Error>) {
guard !isCanceled else {
didFinish()
return
}
switch result {
case .success(let streamIDs):
storedUpdatedArticleIDs.formUnion(streamIDs.ids)
guard let continuation = streamIDs.continuation else {
os_log(.debug, log: log, "%{public}i articles updated since last successful sync start date.", storedUpdatedArticleIDs.count)
Task { @MainActor in
guard let date = newerThan else {
os_log(.debug, log: log, "No date provided so everything must be new (nothing is updated).")
didFinish()
return
}
getStreamIDs(continuation)
case .failure(let error):
didFinish(with: error)
do {
let streamIDs = try await service.getStreamIDs(for: resource, continuation: continuation, newerThan: date, unreadOnly: nil)
storedUpdatedArticleIDs.formUnion(streamIDs.ids)
guard let continuation = streamIDs.continuation else {
os_log(.debug, log: log, "%{public}i articles updated since last successful sync start date.", storedUpdatedArticleIDs.count)
didFinish()
return
}
getStreamIDs(continuation)
} catch {
didFinish(with: error)
}
}
}
}

View File

@ -9,5 +9,6 @@
import Foundation
public protocol FeedlyGetStreamIDsService: AnyObject {
func getStreamIDs(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result<FeedlyStreamIDs, Error>) -> ())
@MainActor func getStreamIDs(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?) async throws -> FeedlyStreamIDs
}