From d37f70d2ddef432d9c057a88844c4560ebe564cb Mon Sep 17 00:00:00 2001 From: Anh Do Date: Tue, 10 Mar 2020 21:08:56 -0400 Subject: [PATCH] Add unread story hashes query --- .../Account/Account.xcodeproj/project.pbxproj | 8 +++ .../NewsBlur/Models/NewsBlurArticle.swift | 50 ++++++++++++++ .../Models/NewsBlurGenericCodingKeys.swift | 25 +++++++ .../Models/NewsBlurSubscription.swift | 20 +----- .../Account/NewsBlur/NewsBlurAPICaller.swift | 33 ++++++++- .../NewsBlur/NewsBlurAccountDelegate.swift | 69 ++++++++++++++++++- 6 files changed, 181 insertions(+), 24 deletions(-) create mode 100644 Frameworks/Account/NewsBlur/Models/NewsBlurArticle.swift create mode 100644 Frameworks/Account/NewsBlur/Models/NewsBlurGenericCodingKeys.swift diff --git a/Frameworks/Account/Account.xcodeproj/project.pbxproj b/Frameworks/Account/Account.xcodeproj/project.pbxproj index 9b18a5cfa..2ae45d270 100644 --- a/Frameworks/Account/Account.xcodeproj/project.pbxproj +++ b/Frameworks/Account/Account.xcodeproj/project.pbxproj @@ -7,7 +7,9 @@ objects = { /* Begin PBXBuildFile section */ + 179DB02FFBC17AC9798F0EBC /* NewsBlurArticle.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB7399814F6FB3247825C /* NewsBlurArticle.swift */; }; 179DB28CF49F73A945EBF5DB /* NewsBlurLoginResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB088236E3236010462E8 /* NewsBlurLoginResponse.swift */; }; + 179DB49A960F8B78C4924458 /* NewsBlurGenericCodingKeys.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB66D933E976C29159DEE /* NewsBlurGenericCodingKeys.swift */; }; 179DBF4DE2562D4C532F6008 /* NewsBlurSubscription.swift in Sources */ = {isa = PBXBuildFile; fileRef = 179DB1B909672E0E807B5E8C /* NewsBlurSubscription.swift */; }; 3B3A33E7238D3D6800314204 /* Secrets.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B3A33E6238D3D6800314204 /* Secrets.swift */; }; 3B826DA72385C81C00FC1ADB /* FeedWranglerAuthorizationResult.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3B826D9E2385C81C00FC1ADB /* FeedWranglerAuthorizationResult.swift */; }; @@ -226,6 +228,8 @@ /* Begin PBXFileReference section */ 179DB088236E3236010462E8 /* NewsBlurLoginResponse.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurLoginResponse.swift; sourceTree = ""; }; 179DB1B909672E0E807B5E8C /* NewsBlurSubscription.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurSubscription.swift; sourceTree = ""; }; + 179DB66D933E976C29159DEE /* NewsBlurGenericCodingKeys.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurGenericCodingKeys.swift; sourceTree = ""; }; + 179DB7399814F6FB3247825C /* NewsBlurArticle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewsBlurArticle.swift; sourceTree = ""; }; 3B3A33E6238D3D6800314204 /* Secrets.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = Secrets.swift; path = ../../Shared/Secrets.swift; sourceTree = ""; }; 3B826D9E2385C81C00FC1ADB /* FeedWranglerAuthorizationResult.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerAuthorizationResult.swift; sourceTree = ""; }; 3B826D9F2385C81C00FC1ADB /* FeedWranglerFeedItem.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FeedWranglerFeedItem.swift; sourceTree = ""; }; @@ -448,6 +452,8 @@ children = ( 179DB088236E3236010462E8 /* NewsBlurLoginResponse.swift */, 179DB1B909672E0E807B5E8C /* NewsBlurSubscription.swift */, + 179DB7399814F6FB3247825C /* NewsBlurArticle.swift */, + 179DB66D933E976C29159DEE /* NewsBlurGenericCodingKeys.swift */, ); path = Models; sourceTree = ""; @@ -1139,6 +1145,8 @@ 769F2BA02EF5F329CDE45F5A /* NewsBlurAPICaller.swift in Sources */, 179DB28CF49F73A945EBF5DB /* NewsBlurLoginResponse.swift in Sources */, 179DBF4DE2562D4C532F6008 /* NewsBlurSubscription.swift in Sources */, + 179DB02FFBC17AC9798F0EBC /* NewsBlurArticle.swift in Sources */, + 179DB49A960F8B78C4924458 /* NewsBlurGenericCodingKeys.swift in Sources */, ); runOnlyForDeploymentPostprocessing = 0; }; diff --git a/Frameworks/Account/NewsBlur/Models/NewsBlurArticle.swift b/Frameworks/Account/NewsBlur/Models/NewsBlurArticle.swift new file mode 100644 index 000000000..8f4f88986 --- /dev/null +++ b/Frameworks/Account/NewsBlur/Models/NewsBlurArticle.swift @@ -0,0 +1,50 @@ +// +// NewsBlurArticle.swift +// Account +// +// Created by Anh Quang Do on 2020-03-10. +// Copyright (c) 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation +import RSCore +import RSParser + +typealias NewsBlurArticleHash = NewsBlurUnreadArticleHashesResponse.ArticleHash + +struct NewsBlurUnreadArticleHashesResponse: Decodable { + let subscriptions: [String: [ArticleHash]] + + struct ArticleHash: Hashable, Codable { + var hash: String + var timestamp: Date + } +} + +extension NewsBlurUnreadArticleHashesResponse { + private enum CodingKeys: String, CodingKey { + case feeds = "unread_feed_story_hashes" + } + + init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + + // Parse subscriptions + var subscriptions: [String: [ArticleHash]] = [:] + let subscriptionContainer = try container.nestedContainer(keyedBy: NewsBlurGenericCodingKeys.self, forKey: .feeds) + try subscriptionContainer.allKeys.forEach { key in + subscriptions[key.stringValue] = [] + var hashArrayContainer = try subscriptionContainer.nestedUnkeyedContainer(forKey: key) + while !hashArrayContainer.isAtEnd { + var hashContainer = try hashArrayContainer.nestedUnkeyedContainer() + let hash = try hashContainer.decode(String.self) + let timestamp = try hashContainer.decode(Date.self) + let articleHash = ArticleHash(hash: hash, timestamp: timestamp) + + subscriptions[key.stringValue]?.append(articleHash) + } + } + + self.subscriptions = subscriptions + } +} diff --git a/Frameworks/Account/NewsBlur/Models/NewsBlurGenericCodingKeys.swift b/Frameworks/Account/NewsBlur/Models/NewsBlurGenericCodingKeys.swift new file mode 100644 index 000000000..99ecaa89f --- /dev/null +++ b/Frameworks/Account/NewsBlur/Models/NewsBlurGenericCodingKeys.swift @@ -0,0 +1,25 @@ +// +// NewsBlurGenericCodingKeys.swift +// Account +// +// Created by Anh Quang Do on 2020-03-10. +// Copyright (c) 2020 Ranchero Software, LLC. All rights reserved. +// + +import Foundation + +struct NewsBlurGenericCodingKeys: CodingKey { + var stringValue: String + + init?(stringValue: String) { + self.stringValue = stringValue + } + + var intValue: Int? { + return nil + } + + init?(intValue: Int) { + return nil + } +} diff --git a/Frameworks/Account/NewsBlur/Models/NewsBlurSubscription.swift b/Frameworks/Account/NewsBlur/Models/NewsBlurSubscription.swift index e10ba0099..35b9a8d6c 100644 --- a/Frameworks/Account/NewsBlur/Models/NewsBlurSubscription.swift +++ b/Frameworks/Account/NewsBlur/Models/NewsBlurSubscription.swift @@ -42,7 +42,7 @@ extension NewsBlurFeedsResponse { // Parse subscriptions var subscriptions: [Subscription] = [] - let subscriptionContainer = try container.nestedContainer(keyedBy: GenericCodingKeys.self, forKey: .feeds) + let subscriptionContainer = try container.nestedContainer(keyedBy: NewsBlurGenericCodingKeys.self, forKey: .feeds) try subscriptionContainer.allKeys.forEach { key in let subscription = try subscriptionContainer.decode(Subscription.self, forKey: key) subscriptions.append(subscription) @@ -50,7 +50,7 @@ extension NewsBlurFeedsResponse { // Parse folders var folders: [Folder] = [] - let folderContainer = try container.nestedContainer(keyedBy: GenericCodingKeys.self, forKey: .folders) + let folderContainer = try container.nestedContainer(keyedBy: NewsBlurGenericCodingKeys.self, forKey: .folders) try folderContainer.allKeys.forEach { key in let subscriptionIds = try folderContainer.decode([Int].self, forKey: key) let folder = Folder(name: key.stringValue, subscriptionIds: subscriptionIds) @@ -72,19 +72,3 @@ extension NewsBlurFeedsResponse.Subscription { case favicon = "favicon_url" } } - -fileprivate struct GenericCodingKeys: CodingKey { - var stringValue: String - - init?(stringValue: String) { - self.stringValue = stringValue - } - - var intValue: Int? { - return nil - } - - init?(intValue: Int) { - return nil - } -} diff --git a/Frameworks/Account/NewsBlur/NewsBlurAPICaller.swift b/Frameworks/Account/NewsBlur/NewsBlurAPICaller.swift index 5039e5f0e..a24cbc741 100644 --- a/Frameworks/Account/NewsBlur/NewsBlurAPICaller.swift +++ b/Frameworks/Account/NewsBlur/NewsBlurAPICaller.swift @@ -84,7 +84,7 @@ final class NewsBlurAPICaller: NSObject { } } - func retrieveSubscriptions(completion: @escaping (Result<[NewsBlurSubscription]?, Error>) -> Void) { + func retrieveSubscriptions(completion: @escaping (Result<[NewsBlurSubscription], Error>) -> Void) { let url = baseURL .appendingPathComponent("reader/feeds") .appendingQueryItems([ @@ -100,8 +100,35 @@ final class NewsBlurAPICaller: NSObject { let request = URLRequest(url: callURL, credentials: credentials) transport.send(request: request, resultType: NewsBlurFeedsResponse.self) { result in switch result { - case .success(let (response, payload)): - print(payload) + case .success((_, let payload)): + completion(.success(payload?.subscriptions ?? [])) + case .failure(let error): + completion(.failure(error)) + } + } + } + + func retrieveUnreadArticleHashes(completion: @escaping (Result<[NewsBlurArticleHash], Error>) -> Void) { + let url = baseURL + .appendingPathComponent("reader/unread_story_hashes") + .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: NewsBlurUnreadArticleHashesResponse.self, dateDecoding: .secondsSince1970) { result in + switch result { + case .success((_, let payload)): + guard let subscriptions = payload?.subscriptions else { + completion(.success([])) + return + } + completion(.success(subscriptions.values.flatMap { $0 })) case .failure(let error): completion(.failure(error)) } diff --git a/Frameworks/Account/NewsBlur/NewsBlurAccountDelegate.swift b/Frameworks/Account/NewsBlur/NewsBlurAccountDelegate.swift index 991f55ab6..203400bbf 100644 --- a/Frameworks/Account/NewsBlur/NewsBlurAccountDelegate.swift +++ b/Frameworks/Account/NewsBlur/NewsBlurAccountDelegate.swift @@ -58,11 +58,57 @@ final class NewsBlurAccountDelegate: AccountDelegate { } func refreshAll(for account: Account, completion: @escaping (Result) -> ()) { - self.refreshProgress.addToNumberOfTasks(1) + self.refreshProgress.addToNumberOfTasksAndRemaining(5) + refreshSubscriptions(for: account) { result in + self.refreshProgress.completeTask() + switch result { case .success: - completion(.success(())) + self.sendArticleStatus(for: account) { result in + self.refreshProgress.completeTask() + + switch result { + case .success: + self.refreshArticleStatus(for: account) { result in + self.refreshProgress.completeTask() + + switch result { + case .success: + self.refreshArticles(for: account) { result in + self.refreshProgress.completeTask() + + switch result { + case .success: + self.refreshMissingArticles(for: account) { result in + self.refreshProgress.completeTask() + + switch result { + case .success: + DispatchQueue.main.async { + completion(.success(())) + } + + case .failure(let error): + completion(.failure(error)) + } + } + + case .failure(let error): + completion(.failure(error)) + } + } + + case .failure(let error): + completion(.failure(error)) + } + } + + case .failure(let error): + completion(.failure(error)) + } + } + case .failure(let error): completion(.failure(error)) } @@ -77,6 +123,23 @@ final class NewsBlurAccountDelegate: AccountDelegate { completion(.success(())) } + func refreshArticles(for account: Account, completion: @escaping (Result<[NewsBlurArticleHash], Error>) -> Void) { + os_log(.debug, log: log, "Refreshing articles...") + + caller.retrieveUnreadArticleHashes { result in + switch result { + case .success(let articleHashes): + print(articleHashes) + case .failure(let error): + break + } + } + } + + func refreshMissingArticles(for account: Account, completion: @escaping (Result)-> Void) { + completion(.success(())) + } + func importOPML(for account: Account, opmlFile: URL, completion: @escaping (Result) -> ()) { completion(.success(())) } @@ -156,11 +219,11 @@ final class NewsBlurAccountDelegate: AccountDelegate { extension NewsBlurAccountDelegate { private func refreshSubscriptions(for account: Account, completion: @escaping (Result) -> Void) { os_log(.debug, log: log, "Refreshing subscriptions...") + caller.retrieveSubscriptions { result in switch result { case .success(let subscriptions): print(subscriptions) - self.refreshProgress.completeTask() completion(.success(())) case .failure(let error): completion(.failure(error))