Make fetching article IDs async — use a callback rather than a fetch sync and a returned value.

This commit is contained in:
Brent Simmons 2019-07-07 15:05:36 -07:00
parent a25436cea1
commit 36791fc3ad
5 changed files with 91 additions and 123 deletions

View File

@ -533,16 +533,16 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), callback: callback) database.fetchStarredAndUnreadCount(for: flattenedFeeds().feedIDs(), callback: callback)
} }
public func fetchUnreadArticleIDs() -> Set<String> { public func fetchUnreadArticleIDs(_ callback: @escaping (Set<String>) -> Void) {
return database.fetchUnreadArticleIDs() database.fetchUnreadArticleIDs(callback)
} }
public func fetchStarredArticleIDs() -> Set<String> { public func fetchStarredArticleIDs(_ callback: @escaping (Set<String>) -> Void) {
return database.fetchStarredArticleIDs() database.fetchStarredArticleIDs(callback)
} }
public func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> { public func fetchArticleIDsForStatusesWithoutArticles(_ callback: @escaping (Set<String>) -> Void) {
return database.fetchArticleIDsForStatusesWithoutArticles() database.fetchArticleIDsForStatusesWithoutArticles(callback)
} }
public func opmlDocument() -> String { public func opmlDocument() -> String {
@ -615,11 +615,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
@discardableResult
func update(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? { func update(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<Article>? {
// Returns set of Articles whose statuses did change. // Returns set of Articles whose statuses did change.
guard !articles.isEmpty, let updatedStatuses = database.mark(articles, statusKey: statusKey, flag: flag) else {
guard let updatedStatuses = database.mark(articles, statusKey: statusKey, flag: flag) else {
return nil return nil
} }
@ -632,7 +631,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
func ensureStatuses(_ articleIDs: Set<String>, _ statusKey: ArticleStatus.Key, _ flag: Bool) { func ensureStatuses(_ articleIDs: Set<String>, _ statusKey: ArticleStatus.Key, _ flag: Bool) {
database.ensureStatuses(articleIDs, statusKey, flag) if !articleIDs.isEmpty {
database.ensureStatuses(articleIDs, statusKey, flag)
}
} }
// MARK: - Container // MARK: - Container

View File

@ -993,33 +993,29 @@ private extension FeedbinAccountDelegate {
} }
func refreshMissingArticles(_ account: Account, completion: @escaping (() -> Void)) { func refreshMissingArticles(_ account: Account, completion: @escaping (() -> Void)) {
os_log(.debug, log: log, "Refreshing missing articles...") os_log(.debug, log: log, "Refreshing missing articles...")
let articleIDs = Array(account.fetchArticleIDsForStatusesWithoutArticles())
let group = DispatchGroup() let group = DispatchGroup()
let chunkedArticleIDs = articleIDs.chunked(into: 100) account.fetchArticleIDsForStatusesWithoutArticles { (fetchedArticleIDs) in
let articleIDs = Array(fetchedArticleIDs)
let chunkedArticleIDs = articleIDs.chunked(into: 100)
for chunk in chunkedArticleIDs {
group.enter()
self.caller.retrieveEntries(articleIDs: chunk) { result in
for chunk in chunkedArticleIDs { switch result {
case .success(let entries):
group.enter() self.processEntries(account: account, entries: entries) {
caller.retrieveEntries(articleIDs: chunk) { result in group.leave()
}
switch result { case .failure(let error):
case .success(let entries): os_log(.error, log: self.log, "Refresh missing articles failed: %@.", error.localizedDescription)
self.processEntries(account: account, entries: entries) {
group.leave() group.leave()
} }
case .failure(let error):
os_log(.error, log: self.log, "Refresh missing articles failed: %@.", error.localizedDescription)
group.leave()
} }
} }
} }
group.notify(queue: DispatchQueue.main) { group.notify(queue: DispatchQueue.main) {
@ -1027,7 +1023,6 @@ private extension FeedbinAccountDelegate {
os_log(.debug, log: self.log, "Done refreshing missing articles.") os_log(.debug, log: self.log, "Done refreshing missing articles.")
completion() completion()
} }
} }
func refreshArticles(_ account: Account, page: String?, completion: @escaping (() -> Void)) { func refreshArticles(_ account: Account, page: String?, completion: @escaping (() -> Void)) {
@ -1101,89 +1096,65 @@ private extension FeedbinAccountDelegate {
} }
func syncArticleReadState(account: Account, articleIDs: [Int]?) { func syncArticleReadState(account: Account, articleIDs: [Int]?) {
guard let articleIDs = articleIDs else { guard let articleIDs = articleIDs else {
return return
} }
let feedbinUnreadArticleIDs = Set(articleIDs.map { String($0) } ) let feedbinUnreadArticleIDs = Set(articleIDs.map { String($0) } )
let currentUnreadArticleIDs = account.fetchUnreadArticleIDs() account.fetchUnreadArticleIDs { (currentUnreadArticleIDs) in
// Mark articles as unread
let deltaUnreadArticleIDs = feedbinUnreadArticleIDs.subtracting(currentUnreadArticleIDs)
account.fetchArticlesAsync(.articleIDs(deltaUnreadArticleIDs)) { (markUnreadArticles) in
account.update(markUnreadArticles, statusKey: .read, flag: false)
// Mark articles as unread // Save any unread statuses for articles we haven't yet received
let deltaUnreadArticleIDs = feedbinUnreadArticleIDs.subtracting(currentUnreadArticleIDs) let markUnreadArticleIDs = Set(markUnreadArticles.map { $0.articleID })
let markUnreadArticles = account.fetchArticles(.articleIDs(deltaUnreadArticleIDs)) let missingUnreadArticleIDs = deltaUnreadArticleIDs.subtracting(markUnreadArticleIDs)
DispatchQueue.main.async {
_ = account.update(markUnreadArticles, statusKey: .read, flag: false)
}
// Save any unread statuses for articles we haven't yet received
let markUnreadArticleIDs = Set(markUnreadArticles.map { $0.articleID })
let missingUnreadArticleIDs = deltaUnreadArticleIDs.subtracting(markUnreadArticleIDs)
if !missingUnreadArticleIDs.isEmpty {
DispatchQueue.main.async {
account.ensureStatuses(missingUnreadArticleIDs, .read, false) account.ensureStatuses(missingUnreadArticleIDs, .read, false)
} }
}
// Mark articles as read // Mark articles as read
let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(feedbinUnreadArticleIDs) let deltaReadArticleIDs = currentUnreadArticleIDs.subtracting(feedbinUnreadArticleIDs)
let markReadArticles = account.fetchArticles(.articleIDs(deltaReadArticleIDs)) account.fetchArticlesAsync(.articleIDs(deltaReadArticleIDs)) { (markReadArticles) in
DispatchQueue.main.async { account.update(markReadArticles, statusKey: .read, flag: true)
_ = account.update(markReadArticles, statusKey: .read, flag: true)
}
// Save any read statuses for articles we haven't yet received // Save any read statuses for articles we haven't yet received
let markReadArticleIDs = Set(markReadArticles.map { $0.articleID }) let markReadArticleIDs = Set(markReadArticles.map { $0.articleID })
let missingReadArticleIDs = deltaReadArticleIDs.subtracting(markReadArticleIDs) let missingReadArticleIDs = deltaReadArticleIDs.subtracting(markReadArticleIDs)
if !missingReadArticleIDs.isEmpty {
DispatchQueue.main.async {
account.ensureStatuses(missingReadArticleIDs, .read, true) account.ensureStatuses(missingReadArticleIDs, .read, true)
} }
} }
} }
func syncArticleStarredState(account: Account, articleIDs: [Int]?) { func syncArticleStarredState(account: Account, articleIDs: [Int]?) {
guard let articleIDs = articleIDs else { guard let articleIDs = articleIDs else {
return return
} }
let feedbinStarredArticleIDs = Set(articleIDs.map { String($0) } ) let feedbinStarredArticleIDs = Set(articleIDs.map { String($0) } )
let currentStarredArticleIDs = account.fetchStarredArticleIDs() account.fetchStarredArticleIDs { (currentStarredArticleIDs) in
// Mark articles as starred
let deltaStarredArticleIDs = feedbinStarredArticleIDs.subtracting(currentStarredArticleIDs)
account.fetchArticlesAsync(.articleIDs(deltaStarredArticleIDs)) { (markStarredArticles) in
account.update(markStarredArticles, statusKey: .starred, flag: true)
// Mark articles as starred // Save any starred statuses for articles we haven't yet received
let deltaStarredArticleIDs = feedbinStarredArticleIDs.subtracting(currentStarredArticleIDs) let markStarredArticleIDs = Set(markStarredArticles.map { $0.articleID })
let markStarredArticles = account.fetchArticles(.articleIDs(deltaStarredArticleIDs)) let missingStarredArticleIDs = deltaStarredArticleIDs.subtracting(markStarredArticleIDs)
DispatchQueue.main.async {
_ = account.update(markStarredArticles, statusKey: .starred, flag: true)
}
// Save any starred statuses for articles we haven't yet received
let markStarredArticleIDs = Set(markStarredArticles.map { $0.articleID })
let missingStarredArticleIDs = deltaStarredArticleIDs.subtracting(markStarredArticleIDs)
if !missingStarredArticleIDs.isEmpty {
DispatchQueue.main.async {
account.ensureStatuses(missingStarredArticleIDs, .starred, true) account.ensureStatuses(missingStarredArticleIDs, .starred, true)
} }
}
// Mark articles as unstarred // Mark articles as unstarred
let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(feedbinStarredArticleIDs) let deltaUnstarredArticleIDs = currentStarredArticleIDs.subtracting(feedbinStarredArticleIDs)
let markUnstarredArticles = account.fetchArticles(.articleIDs(deltaUnstarredArticleIDs)) account.fetchArticlesAsync(.articleIDs(deltaUnstarredArticleIDs)) { (markUnstarredArticles) in
DispatchQueue.main.async { account.update(markUnstarredArticles, statusKey: .starred, flag: false)
_ = account.update(markUnstarredArticles, statusKey: .starred, flag: false)
}
// Save any unstarred statuses for articles we haven't yet received // Save any unstarred statuses for articles we haven't yet received
let markUnstarredArticleIDs = Set(markUnstarredArticles.map { $0.articleID }) let markUnstarredArticleIDs = Set(markUnstarredArticles.map { $0.articleID })
let missingUnstarredArticleIDs = deltaUnstarredArticleIDs.subtracting(markUnstarredArticleIDs) let missingUnstarredArticleIDs = deltaUnstarredArticleIDs.subtracting(markUnstarredArticleIDs)
if !missingUnstarredArticleIDs.isEmpty {
DispatchQueue.main.async {
account.ensureStatuses(missingUnstarredArticleIDs, .starred, false) account.ensureStatuses(missingUnstarredArticleIDs, .starred, false)
} }
} }
} }
func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) { func deleteTagging(for account: Account, with feed: Feed, from container: Container?, completion: @escaping (Result<Void, Error>) -> Void) {

View File

@ -128,16 +128,16 @@ public final class ArticlesDatabase {
// MARK: - Status // MARK: - Status
public func fetchUnreadArticleIDs() -> Set<String> { public func fetchUnreadArticleIDs(_ callback: @escaping (Set<String>) -> Void) {
return articlesTable.fetchUnreadArticleIDs() articlesTable.fetchUnreadArticleIDs(callback)
} }
public func fetchStarredArticleIDs() -> Set<String> { public func fetchStarredArticleIDs(_ callback: @escaping (Set<String>) -> Void) {
return articlesTable.fetchStarredArticleIDs() articlesTable.fetchStarredArticleIDs(callback)
} }
public func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> { public func fetchArticleIDsForStatusesWithoutArticles(_ callback: @escaping (Set<String>) -> Void) {
return articlesTable.fetchArticleIDsForStatusesWithoutArticles() articlesTable.fetchArticleIDsForStatusesWithoutArticles(callback)
} }
public func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<ArticleStatus>? { public func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<ArticleStatus>? {

View File

@ -384,16 +384,16 @@ final class ArticlesTable: DatabaseTable {
// MARK: Status // MARK: Status
func fetchUnreadArticleIDs() -> Set<String> { func fetchUnreadArticleIDs(_ callback: @escaping (Set<String>) -> Void) {
return statusesTable.fetchUnreadArticleIDs() statusesTable.fetchUnreadArticleIDs(callback)
} }
func fetchStarredArticleIDs() -> Set<String> { func fetchStarredArticleIDs(_ callback: @escaping (Set<String>) -> Void) {
return statusesTable.fetchStarredArticleIDs() statusesTable.fetchStarredArticleIDs(callback)
} }
func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> { func fetchArticleIDsForStatusesWithoutArticles(_ callback: @escaping (Set<String>) -> Void) {
return statusesTable.fetchArticleIDsForStatusesWithoutArticles() statusesTable.fetchArticleIDsForStatusesWithoutArticles(callback)
} }
func mark(_ articles: Set<Article>, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set<ArticleStatus>? { func mark(_ articles: Set<Article>, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set<ArticleStatus>? {

View File

@ -77,31 +77,27 @@ final class StatusesTable: DatabaseTable {
// MARK: Fetching // MARK: Fetching
func fetchUnreadArticleIDs() -> Set<String> { func fetchUnreadArticleIDs(_ callback: @escaping (Set<String>) -> Void) {
return fetchArticleIDs("select articleID from statuses where read=0 and userDeleted=0;") fetchArticleIDs("select articleID from statuses where read=0 and userDeleted=0;", callback)
} }
func fetchStarredArticleIDs() -> Set<String> { func fetchStarredArticleIDs(_ callback: @escaping (Set<String>) -> Void) {
return fetchArticleIDs("select articleID from statuses where starred=1 and userDeleted=0;") fetchArticleIDs("select articleID from statuses where starred=1 and userDeleted=0;", callback)
} }
func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> { func fetchArticleIDsForStatusesWithoutArticles(_ callback: @escaping (Set<String>) -> Void) {
return fetchArticleIDs("select articleID from statuses s where (read=0 or starred=1) and userDeleted=0 and not exists (select 1 from articles a where a.articleID = s.articleID);") fetchArticleIDs("select articleID from statuses s where (read=0 or starred=1) and userDeleted=0 and not exists (select 1 from articles a where a.articleID = s.articleID);", callback)
} }
func fetchArticleIDs(_ sql: String) -> Set<String> { func fetchArticleIDs(_ sql: String, _ callback: @escaping (Set<String>) -> Void) {
queue.fetch { (database) in
var statuses: Set<String>? = nil guard let resultSet = database.executeQuery(sql, withArgumentsIn: nil) else {
callback(Set<String>())
queue.fetchSync { (database) in return
if let resultSet = database.executeQuery(sql, withArgumentsIn: nil) {
statuses = resultSet.mapToSet(self.articleIDWithRow)
} }
let statuses = resultSet.mapToSet(self.articleIDWithRow)
callback(statuses)
} }
return statuses != nil ? statuses! : Set<String>()
} }
func articleIDWithRow(_ row: FMResultSet) -> String? { func articleIDWithRow(_ row: FMResultSet) -> String? {