diff --git a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift index 857ef3fd1..fdda966a2 100644 --- a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift +++ b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift @@ -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) -> ()) { - 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 } } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyIngestStarredArticleIDsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyIngestStarredArticleIDsOperation.swift index 481869b1c..3d09b99bb 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyIngestStarredArticleIDsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyIngestStarredArticleIDsOperation.swift @@ -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) { - 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) { + func processStarredArticleIDs(_ localStarredArticleIDs: Set) 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 } } } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyIngestStreamArticleIDsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyIngestStreamArticleIDsOperation.swift index 860abf149..e7958c064 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyIngestStreamArticleIDsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyIngestStreamArticleIDsOperation.swift @@ -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) { - 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) } } } diff --git a/Account/Sources/Account/Feedly/Operations/FeedlyIngestUnreadArticleIDsOperation.swift b/Account/Sources/Account/Feedly/Operations/FeedlyIngestUnreadArticleIDsOperation.swift index 10e0969cf..e8f691786 100644 --- a/Account/Sources/Account/Feedly/Operations/FeedlyIngestUnreadArticleIDsOperation.swift +++ b/Account/Sources/Account/Feedly/Operations/FeedlyIngestUnreadArticleIDsOperation.swift @@ -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) { - 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) { - - guard !isCanceled else { - didFinish() - return - } + private func processUnreadArticleIDs(_ localUnreadArticleIDs: Set) 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 } } } diff --git a/Feedly/Sources/Feedly/Operations/FeedlyGetStreamIDsOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyGetStreamIDsOperation.swift index de8ea44a8..2aaa4d71e 100644 --- a/Feedly/Sources/Feedly/Operations/FeedlyGetStreamIDsOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyGetStreamIDsOperation.swift @@ -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) } diff --git a/Feedly/Sources/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift index 383230b64..0fc52ab60 100644 --- a/Feedly/Sources/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlyGetUpdatedArticleIDsOperation.swift @@ -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) { - 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) + } } } } diff --git a/Feedly/Sources/Feedly/Services/FeedlyGetStreamIDsService.swift b/Feedly/Sources/Feedly/Services/FeedlyGetStreamIDsService.swift index 7205ddf16..889419965 100644 --- a/Feedly/Sources/Feedly/Services/FeedlyGetStreamIDsService.swift +++ b/Feedly/Sources/Feedly/Services/FeedlyGetStreamIDsService.swift @@ -9,5 +9,6 @@ import Foundation public protocol FeedlyGetStreamIDsService: AnyObject { - func getStreamIDs(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?, completion: @escaping (Result) -> ()) + + @MainActor func getStreamIDs(for resource: FeedlyResourceID, continuation: String?, newerThan: Date?, unreadOnly: Bool?) async throws -> FeedlyStreamIDs }