From b7e7e176568e09bd45c8d4c6397f87ab5c536e50 Mon Sep 17 00:00:00 2001 From: Anh Do Date: Sat, 14 Mar 2020 16:10:05 -0400 Subject: [PATCH] Update unread/starred status --- .../Account/NewsBlur/NewsBlurAPICaller.swift | 64 +++++----- .../NewsBlur/NewsBlurAccountDelegate.swift | 110 +++++++++++++++++- 2 files changed, 143 insertions(+), 31 deletions(-) diff --git a/Frameworks/Account/NewsBlur/NewsBlurAPICaller.swift b/Frameworks/Account/NewsBlur/NewsBlurAPICaller.swift index c139c76fa..d13418b8a 100644 --- a/Frameworks/Account/NewsBlur/NewsBlurAPICaller.swift +++ b/Frameworks/Account/NewsBlur/NewsBlurAPICaller.swift @@ -138,38 +138,20 @@ final class NewsBlurAPICaller: NSObject { } func retrieveUnreadStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) { - let url = baseURL - .appendingPathComponent("reader/unread_story_hashes") - .appendingQueryItems([ - URLQueryItem(name: "include_timestamps", value: "true"), - ]) + retrieveStoryHashes(endpoint: "reader/unread_story_hashes", completion: completion) + } - guard let callURL = url else { - completion(.failure(TransportError.noURL)) - return - } - - let request = URLRequest(url: callURL, credentials: credentials) - transport.send(request: request, resultType: NewsBlurUnreadStoryHashesResponse.self, dateDecoding: .secondsSince1970) { result in - if self.suspended { - completion(.failure(TransportError.suspended)) - return - } - - switch result { - case .success((_, let payload)): - completion(.success(payload?.feeds.values.flatMap { $0 })) - case .failure(let error): - completion(.failure(error)) - } - } + func retrieveStarredStoryHashes(completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) { + retrieveStoryHashes(endpoint: "reader/starred_story_hashes", completion: completion) } func retrieveStories(hashes: [NewsBlurStoryHash], completion: @escaping (Result<[NewsBlurStory]?, Error>) -> Void) { let url = baseURL .appendingPathComponent("reader/river_stories") .appendingQueryItem(.init(name: "include_hidden", value: "true"))? - .appendingQueryItems(hashes.map { URLQueryItem(name: "h", value: $0.hash) }) + .appendingQueryItems(hashes.map { + URLQueryItem(name: "h", value: $0.hash) + }) guard let callURL = url else { completion(.failure(TransportError.noURL)) @@ -207,7 +189,37 @@ final class NewsBlurAPICaller: NSObject { func unstar(hashes: [String], completion: @escaping (Result) -> Void) { sendStoryStatus(endpoint: "reader/mark_story_hash_as_unstarred", hashes: hashes, completion: completion) } - +} + +extension NewsBlurAPICaller { + private func retrieveStoryHashes(endpoint: String, completion: @escaping (Result<[NewsBlurStoryHash]?, Error>) -> Void) { + let url = baseURL + .appendingPathComponent(endpoint) + .appendingQueryItems([ + URLQueryItem(name: "include_timestamps", value: "true"), + ]) + + guard let callURL = url else { + completion(.failure(TransportError.noURL)) + return + } + + let request = URLRequest(url: callURL, credentials: credentials) + transport.send(request: request, resultType: NewsBlurUnreadStoryHashesResponse.self, dateDecoding: .secondsSince1970) { result in + if self.suspended { + completion(.failure(TransportError.suspended)) + return + } + + switch result { + case .success((_, let payload)): + completion(.success(payload?.feeds.values.flatMap { $0 })) + case .failure(let error): + completion(.failure(error)) + } + } + } + private func sendStoryStatus(endpoint: String, hashes: [String], completion: @escaping (Result) -> Void) { let callURL = baseURL.appendingPathComponent(endpoint) diff --git a/Frameworks/Account/NewsBlur/NewsBlurAccountDelegate.swift b/Frameworks/Account/NewsBlur/NewsBlurAccountDelegate.swift index 1b2b7e87f..d72ea2007 100644 --- a/Frameworks/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Frameworks/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -181,9 +181,45 @@ final class NewsBlurAccountDelegate: AccountDelegate { } func refreshArticleStatus(for account: Account, completion: @escaping (Result) -> ()) { - os_log(.debug, log: log, "Refreshing article statuses...") + os_log(.debug, log: log, "Refreshing story statuses...") - // TODO: Fill this in + let group = DispatchGroup() + var errorOccurred = false + + group.enter() + caller.retrieveUnreadStoryHashes { result in + switch result { + case .success(let storyHashes): + self.syncStoryReadState(account: account, hashes: storyHashes) + group.leave() + case .failure(let error): + errorOccurred = true + os_log(.info, log: self.log, "Retrieving unread stories failed: %@.", error.localizedDescription) + group.leave() + } + } + + group.enter() + caller.retrieveStarredStoryHashes { result in + switch result { + case .success(let storyHashes): + self.syncStoryStarredState(account: account, hashes: storyHashes) + group.leave() + case .failure(let error): + errorOccurred = true + os_log(.info, log: self.log, "Retrieving starred stories failed: %@.", error.localizedDescription) + group.leave() + } + } + + group.notify(queue: DispatchQueue.main) { + os_log(.debug, log: self.log, "Done refreshing article statuses.") + if errorOccurred { + completion(.failure(NewsBlurError.unknown)) + } else { + completion(.success(())) + } + } } func refreshStories(for account: Account, completion: @escaping (Result) -> Void) { @@ -548,9 +584,7 @@ extension NewsBlurAccountDelegate { } private func mapStoriesToParsedItems(stories: [NewsBlurStory]?) -> Set { - guard let stories = stories else { - return Set() - } + guard let stories = stories else { return Set() } let parsedItems: [ParsedItem] = stories.map { story in let author = Set([ParsedAuthor(name: story.authorName, url: nil, avatarURL: nil, emailAddress: nil)]) @@ -598,4 +632,70 @@ extension NewsBlurAccountDelegate { } } } + + private func syncStoryReadState(account: Account, hashes: [NewsBlurStoryHash]?) { + guard let hashes = hashes else { return } + + database.selectPendingReadStatusArticleIDs() { result in + func process(_ pendingStoryHashes: Set) { + + let newsBlurUnreadStoryHashes = Set(hashes.map { $0.hash } ) + let updatableNewsBlurUnreadStoryHashes = newsBlurUnreadStoryHashes.subtracting(pendingStoryHashes) + + account.fetchUnreadArticleIDs { articleIDsResult in + guard let currentUnreadArticleIDs = try? articleIDsResult.get() else { + return + } + + // Mark articles as unread + let deltaUnreadArticleIDs = updatableNewsBlurUnreadStoryHashes.subtracting(currentUnreadArticleIDs) + account.markAsUnread(deltaUnreadArticleIDs) + + // Mark articles as read + let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(updatableNewsBlurUnreadStoryHashes) + account.markAsRead(deltaReadArticleIDs) + } + } + + switch result { + case .success(let pendingArticleIDs): + process(pendingArticleIDs) + case .failure(let error): + os_log(.error, log: self.log, "Sync Story Read Status failed: %@.", error.localizedDescription) + } + } + } + + private func syncStoryStarredState(account: Account, hashes: [NewsBlurStoryHash]?) { + guard let hashes = hashes else { return } + + database.selectPendingStarredStatusArticleIDs() { result in + func process(_ pendingStoryHashes: Set) { + + let newsBlurStarredStoryHashes = Set(hashes.map { $0.hash } ) + let updatableNewsBlurUnreadStoryHashes = newsBlurStarredStoryHashes.subtracting(pendingStoryHashes) + + account.fetchStarredArticleIDs { articleIDsResult in + guard let currentStarredArticleIDs = try? articleIDsResult.get() else { + return + } + + // Mark articles as starred + let deltaStarredArticleIDs = updatableNewsBlurUnreadStoryHashes.subtracting(currentStarredArticleIDs) + account.markAsStarred(deltaStarredArticleIDs) + + // Mark articles as unstarred + let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(updatableNewsBlurUnreadStoryHashes) + account.markAsUnstarred(deltaUnstarredArticleIDs) + } + } + + switch result { + case .success(let pendingArticleIDs): + process(pendingArticleIDs) + case .failure(let error): + os_log(.error, log: self.log, "Sync Story Starred Status failed: %@.", error.localizedDescription) + } + } + } }