From 9c159d21f6f82d920fa8adca17afc7fa95ac3d5b Mon Sep 17 00:00:00 2001 From: Maurice Parker Date: Fri, 17 May 2019 14:56:27 -0500 Subject: [PATCH] Retrieve articles when we have a status but don't have an article on full refresh. --- Frameworks/Account/Account.swift | 4 ++ .../Account/Feedbin/FeedbinAPICaller.swift | 65 +++++++++++++------ .../Feedbin/FeedbinAccountDelegate.swift | 62 ++++++++++++++++-- .../ArticlesDatabase/ArticlesDatabase.swift | 4 ++ .../ArticlesDatabase/ArticlesTable.swift | 4 ++ .../ArticlesDatabase/StatusesTable.swift | 4 ++ 6 files changed, 119 insertions(+), 24 deletions(-) diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index b25c60103..f5bc4f199 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -569,6 +569,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, return database.fetchStarredArticleIDs() } + public func fetchArticleIDsForStatusesWithoutArticles() -> Set { + return database.fetchArticleIDsForStatusesWithoutArticles() + } + public func opmlDocument() -> String { let escapedTitle = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters() let openingText = diff --git a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift index 695269669..a2400c7f7 100644 --- a/Frameworks/Account/Feedbin/FeedbinAPICaller.swift +++ b/Frameworks/Account/Feedbin/FeedbinAPICaller.swift @@ -347,6 +347,33 @@ final class FeedbinAPICaller: NSObject { } + func retrieveEntries(articleIDs: [String], completion: @escaping (Result<([FeedbinEntry]?), Error>) -> Void) { + + guard !articleIDs.isEmpty else { + completion(.success(([FeedbinEntry]()))) + return + } + + let concatIDs = articleIDs.reduce("") { param, articleID in return param + ",\(articleID)" } + let paramIDs = String(concatIDs.dropFirst()) + + var callURL = URLComponents(url: feedbinBaseURL.appendingPathComponent("entries.json"), resolvingAgainstBaseURL: false)! + callURL.queryItems = [URLQueryItem(name: "ids", value: paramIDs)] + let request = URLRequest(url: callURL.url!, credentials: credentials) + + transport.send(request: request, resultType: [FeedbinEntry].self) { [weak self] result in + + switch result { + case .success(let (_, entries)): + completion(.success((entries))) + case .failure(let error): + completion(.failure(error)) + } + + } + + } + func retrieveEntries(feedID: String, completion: @escaping (Result<([FeedbinEntry]?, String?), Error>) -> Void) { let since = Calendar.current.date(byAdding: .month, value: -3, to: Date()) ?? Date() @@ -407,25 +434,6 @@ final class FeedbinAPICaller: NSObject { } - func extractPageNumber(link: String?) -> Int? { - - guard let link = link else { - return nil - } - - if let lowerBound = link.range(of: "page=")?.upperBound { - if let upperBound = link.range(of: "&")?.lowerBound { - return Int(link[lowerBound..")?.lowerBound { - return Int(link[lowerBound..) -> Void) { guard let callURL = URL(string: page) else { @@ -532,4 +540,23 @@ extension FeedbinAPICaller { } } + func extractPageNumber(link: String?) -> Int? { + + guard let link = link else { + return nil + } + + if let lowerBound = link.range(of: "page=")?.upperBound { + if let upperBound = link.range(of: "&")?.lowerBound { + return Int(link[lowerBound..")?.lowerBound { + return Int(link[lowerBound.. Void)? = nil) { - refreshProgress.addToNumberOfTasksAndRemaining(5) + refreshProgress.addToNumberOfTasksAndRemaining(6) refreshAccount(account) { [weak self] result in switch result { @@ -88,8 +88,12 @@ final class FeedbinAccountDelegate: AccountDelegate { self?.refreshArticles(account) { self?.refreshArticleStatus(for: account) { - self?.refreshProgress.clear() - completion?() + self?.refreshMissingArticles(account) { + self?.refreshProgress.clear() + DispatchQueue.main.async { + completion?() + } + } } } @@ -794,24 +798,32 @@ private extension FeedbinAccountDelegate { return } + let group = DispatchGroup() + let articleIDs = statuses.compactMap { Int($0.articleID) } let articleIDGroups = articleIDs.chunked(into: 1000) for articleIDGroup in articleIDGroups { + group.enter() apiCall(articleIDGroup) { [weak self] result in switch result { case .success: self?.database.deleteSelectedForProcessing(articleIDGroup.map { String($0) } ) - completion() + group.leave() case .failure(let error): guard let self = self else { return } os_log(.error, log: self.log, "Article status sync call failed: %@.", error.localizedDescription) self.database.resetSelectedForProcessing(articleIDGroup.map { String($0) } ) - completion() + group.leave() } } } + + group.notify(queue: DispatchQueue.main) { + completion() + } + } func processRestoredFeed(for account: Account, feed: Feed, editedName: String?, folder: Folder?, completion: @escaping (Result) -> Void) { @@ -991,6 +1003,46 @@ private extension FeedbinAccountDelegate { } + func refreshMissingArticles(_ account: Account, completion: @escaping (() -> Void)) { + + os_log(.debug, log: log, "Refreshing missing articles...") + let articleIDs = Array(account.fetchArticleIDsForStatusesWithoutArticles()) + + let group = DispatchGroup() + + let chunkedArticleIDs = articleIDs.chunked(into: 100) + refreshProgress.addToNumberOfTasks(chunkedArticleIDs.count - 1) + + for chunk in chunkedArticleIDs { + + group.enter() + caller.retrieveEntries(articleIDs: chunk) { [weak self] result in + + switch result { + case .success(let entries): + + self?.processEntries(account: account, entries: entries) { + self?.refreshProgress.completeTask() + group.leave() + } + + case .failure(let error): + guard let self = self else { return } + os_log(.error, log: self.log, "Refresh missing articles failed: %@.", error.localizedDescription) + group.leave() + } + + } + + } + + group.notify(queue: DispatchQueue.main) { + os_log(.debug, log: self.log, "Done refreshing missing articles.") + completion() + } + + } + func refreshArticles(_ account: Account, page: String?, completion: @escaping (() -> Void)) { guard let page = page else { diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index e380c0ed2..6802974b5 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -112,6 +112,10 @@ public final class ArticlesDatabase { return articlesTable.fetchStarredArticleIDs() } + public func fetchArticleIDsForStatusesWithoutArticles() -> Set { + return articlesTable.fetchArticleIDsForStatusesWithoutArticles() + } + public func mark(_ articles: Set
, statusKey: ArticleStatus.Key, flag: Bool) -> Set? { return articlesTable.mark(articles, statusKey, flag) } diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index b337f6b1f..05f1a2794 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -307,6 +307,10 @@ final class ArticlesTable: DatabaseTable { return statusesTable.fetchStarredArticleIDs() } + func fetchArticleIDsForStatusesWithoutArticles() -> Set { + return statusesTable.fetchArticleIDsForStatusesWithoutArticles() + } + func mark(_ articles: Set
, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set? { return statusesTable.mark(articles.statuses(), statusKey, flag) diff --git a/Frameworks/ArticlesDatabase/StatusesTable.swift b/Frameworks/ArticlesDatabase/StatusesTable.swift index 080bb4054..86b29034e 100644 --- a/Frameworks/ArticlesDatabase/StatusesTable.swift +++ b/Frameworks/ArticlesDatabase/StatusesTable.swift @@ -86,6 +86,10 @@ final class StatusesTable: DatabaseTable { return fetchArticleIDs("select articleID from statuses where starred=1 and userDeleted=0;") } + func fetchArticleIDsForStatusesWithoutArticles() -> Set { + return fetchArticleIDs("select articleID from statuses s where userDeleted=0 and not exists (select 1 from articles a where a.articleID = s.articleID);") + } + func fetchArticleIDs(_ sql: String) -> Set { var statuses: Set? = nil