diff --git a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift index 875231d19..38be1b88b 100644 --- a/Account/Sources/Account/Feedly/FeedlyAPICaller.swift +++ b/Account/Sources/Account/Feedly/FeedlyAPICaller.swift @@ -501,67 +501,21 @@ extension FeedlyAPICaller: FeedlyMarkArticlesService { var entryIDs: [String] } - func mark(_ articleIDs: Set, as action: FeedlyMarkAction, completion: @escaping @Sendable (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)) - } - } - var components = baseURLComponents - components.path = "/v3/markers" - - guard let url = components.url else { - fatalError("\(components) does not produce a valid URL.") - } - + func mark(_ articleIDs: Set, as action: FeedlyMarkAction) async throws { + + guard !isSuspended else { throw TransportError.suspended } + let articleIDChunks = Array(articleIDs).chunked(into: 300) - let dispatchGroup = DispatchGroup() - var groupError: Error? = nil for articleIDChunk in articleIDChunks { - var request = URLRequest(url: url) - request.httpMethod = "POST" - request.addValue("application/json", forHTTPHeaderField: HTTPRequestHeader.contentType) - request.addValue("application/json", forHTTPHeaderField: "Accept-Type") - request.addValue("OAuth \(accessToken)", forHTTPHeaderField: HTTPRequestHeader.authorization) - - do { - let body = MarkerEntriesBody(action: action.actionValue, entryIDs: Array(articleIDChunk)) - let encoder = JSONEncoder() - let data = try encoder.encode(body) - request.httpBody = data - } catch { - return DispatchQueue.main.async { - completion(.failure(error)) - } - } - - dispatchGroup.enter() - send(request: request, resultType: String.self, dateDecoding: .millisecondsSince1970, keyDecoding: .convertFromSnakeCase) { result in - switch result { - case .success(let (httpResponse, _)): - if httpResponse.statusCode != 200 { - groupError = URLError(.cannotDecodeContentData) - } - case .failure(let error): - groupError = error - } - dispatchGroup.leave() - } - } - - dispatchGroup.notify(queue: .main) { - if let groupError = groupError { - completion(.failure(groupError)) - } else { - completion(.success(())) + var request = try urlRequest(path: "/v3/markers", method: HTTPMethod.post, includeJSONHeaders: true, includeOAuthToken: true) + let body = MarkerEntriesBody(action: action.actionValue, entryIDs: Array(articleIDChunk)) + try addObject(body, to: &request) + + let (httpResponse, _) = try await send(request: request, resultType: String.self) + if httpResponse.statusCode != 200 { + throw URLError(.cannotDecodeContentData) } } } @@ -569,25 +523,22 @@ extension FeedlyAPICaller: FeedlyMarkArticlesService { extension FeedlyAPICaller: FeedlySearchService { - @MainActor func getFeeds(for query: String, count: Int, locale: String) async throws -> FeedlyFeedsSearchResponse { + func getFeeds(for query: String, count: Int, locale: String) async throws -> FeedlyFeedsSearchResponse { guard !isSuspended else { throw TransportError.suspended } var components = baseURLComponents components.path = "/v3/search/feeds" - components.queryItems = [ URLQueryItem(name: "query", value: query), URLQueryItem(name: "count", value: String(count)), URLQueryItem(name: "locale", value: locale) ] - guard let url = components.url else { fatalError("\(components) does not produce a valid URL.") } var request = URLRequest(url: url) - request.httpMethod = "GET" addJSONHeaders(&request) let (_, searchResponse) = try await send(request: request, resultType: FeedlyFeedsSearchResponse.self) diff --git a/Feedly/Sources/Feedly/Operations/FeedlySendArticleStatusesOperation.swift b/Feedly/Sources/Feedly/Operations/FeedlySendArticleStatusesOperation.swift index 45e9df593..bb1b5320f 100644 --- a/Feedly/Sources/Feedly/Operations/FeedlySendArticleStatusesOperation.swift +++ b/Feedly/Sources/Feedly/Operations/FeedlySendArticleStatusesOperation.swift @@ -42,6 +42,7 @@ public final class FeedlySendArticleStatusesOperation: FeedlyOperation { private extension FeedlySendArticleStatusesOperation { func processStatuses(_ pending: [SyncStatus]) { + let statuses: [(status: SyncStatus.Key, flag: Bool, action: FeedlyMarkAction)] = [ (.read, false, .unread), (.read, true, .read), @@ -49,32 +50,25 @@ private extension FeedlySendArticleStatusesOperation { (.starred, false, .unsaved), ] - let group = DispatchGroup() + Task { @MainActor in - for pairing in statuses { - let articleIDs = pending.filter { $0.key == pairing.status && $0.flag == pairing.flag } - guard !articleIDs.isEmpty else { - continue - } + for pairing in statuses { - let ids = Set(articleIDs.map { $0.articleID }) - let database = self.database - group.enter() - service.mark(ids, as: pairing.action) { result in - Task { @MainActor in - switch result { - case .success: - try? await database.deleteSelectedForProcessing(Array(ids)) - group.leave() - case .failure: - try? await database.resetSelectedForProcessing(Array(ids)) - group.leave() - } + let articleIDs = pending.filter { $0.key == pairing.status && $0.flag == pairing.flag } + guard !articleIDs.isEmpty else { + continue + } + + let ids = Set(articleIDs.map { $0.articleID }) + + do { + try await service.mark(ids, as: pairing.action) + try? await database.deleteSelectedForProcessing(Array(ids)) + } catch { + try? await database.resetSelectedForProcessing(Array(ids)) } } - } - group.notify(queue: DispatchQueue.main) { os_log(.debug, log: self.log, "Done sending article statuses.") self.didFinish() } diff --git a/Feedly/Sources/Feedly/Services/FeedlyMarkArticlesService.swift b/Feedly/Sources/Feedly/Services/FeedlyMarkArticlesService.swift index d6528253c..146c4a2e1 100644 --- a/Feedly/Sources/Feedly/Services/FeedlyMarkArticlesService.swift +++ b/Feedly/Sources/Feedly/Services/FeedlyMarkArticlesService.swift @@ -33,5 +33,5 @@ public enum FeedlyMarkAction: String, Sendable { public protocol FeedlyMarkArticlesService: AnyObject { - func mark(_ articleIDs: Set, as action: FeedlyMarkAction, completion: @escaping @Sendable (Result) -> ()) + @MainActor func mark(_ articleIDs: Set, as action: FeedlyMarkAction) async throws }