Retrieve articles when we have a status but don't have an article on full refresh.

This commit is contained in:
Maurice Parker 2019-05-17 14:56:27 -05:00
parent 0a9bf2aef0
commit 9c159d21f6
6 changed files with 119 additions and 24 deletions

View File

@ -569,6 +569,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return database.fetchStarredArticleIDs()
}
public func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
return database.fetchArticleIDsForStatusesWithoutArticles()
}
public func opmlDocument() -> String {
let escapedTitle = nameForDisplay.rs_stringByEscapingSpecialXMLCharacters()
let openingText =

View File

@ -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..<upperBound])
}
if let upperBound = link.range(of: ">")?.lowerBound {
return Int(link[lowerBound..<upperBound])
}
}
return nil
}
func retrieveEntries(page: String, completion: @escaping (Result<([FeedbinEntry]?, String?), Error>) -> 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..<upperBound])
}
if let upperBound = link.range(of: ">")?.lowerBound {
return Int(link[lowerBound..<upperBound])
}
}
return nil
}
}

View File

@ -80,7 +80,7 @@ final class FeedbinAccountDelegate: AccountDelegate {
func refreshAll(for account: Account, completion: (() -> 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, Error>) -> 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 {

View File

@ -112,6 +112,10 @@ public final class ArticlesDatabase {
return articlesTable.fetchStarredArticleIDs()
}
public func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
return articlesTable.fetchArticleIDsForStatusesWithoutArticles()
}
public func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<ArticleStatus>? {
return articlesTable.mark(articles, statusKey, flag)
}

View File

@ -307,6 +307,10 @@ final class ArticlesTable: DatabaseTable {
return statusesTable.fetchStarredArticleIDs()
}
func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
return statusesTable.fetchArticleIDsForStatusesWithoutArticles()
}
func mark(_ articles: Set<Article>, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set<ArticleStatus>? {
return statusesTable.mark(articles.statuses(), statusKey, flag)

View File

@ -86,6 +86,10 @@ final class StatusesTable: DatabaseTable {
return fetchArticleIDs("select articleID from statuses where starred=1 and userDeleted=0;")
}
func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
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<String> {
var statuses: Set<String>? = nil