Save articles from multiple web feeds at once — rather than doing it feed-by-feed — when syncing. (This makes syncing faster.) Fix #1794.

This commit is contained in:
Brent Simmons 2020-03-22 19:25:53 -07:00
parent 9f516caa35
commit fb4f72ad18
4 changed files with 56 additions and 52 deletions

View File

@ -689,55 +689,48 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return return
} }
let group = DispatchGroup() database.update(webFeedIDsAndItems: webFeedIDsAndItems, defaultRead: defaultRead) { updateArticlesResult in
var possibleError: DatabaseError? = nil
var newArticles = Set<Article>()
var updatedArticles = Set<Article>()
for (webFeedID, items) in webFeedIDsAndItems { func sendNotificationAbout(newArticles: Set<Article>?, updatedArticles: Set<Article>?) {
var webFeeds = Set<WebFeed>()
group.enter() if let newArticles = newArticles {
database.update(webFeedID: webFeedID, items: items, defaultRead: defaultRead) { updateArticlesResult in webFeeds.formUnion(Set(newArticles.compactMap { $0.webFeed }))
}
switch updateArticlesResult { if let updatedArticles = updatedArticles {
case .success(let newAndUpdatedArticles): webFeeds.formUnion(Set(updatedArticles.compactMap { $0.webFeed }))
if let articles = newAndUpdatedArticles.newArticles {
newArticles.formUnion(articles)
}
if let articles = newAndUpdatedArticles.updatedArticles {
updatedArticles.formUnion(articles)
}
case .failure(let databaseError):
possibleError = databaseError
} }
group.leave() var shouldSendNotification = false
} var userInfo = [String: Any]()
} if let newArticles = newArticles, !newArticles.isEmpty {
shouldSendNotification = true
group.notify(queue: DispatchQueue.main) { userInfo[UserInfoKey.newArticles] = newArticles
var userInfo = [String: Any]() self.updateUnreadCounts(for: webFeeds) {
var webFeeds = Set(newArticles.compactMap { $0.webFeed }) NotificationCenter.default.post(name: .DownloadArticlesDidUpdateUnreadCounts, object: self, userInfo: nil)
webFeeds.formUnion(Set(updatedArticles.compactMap { $0.webFeed })) }
}
if !newArticles.isEmpty {
self.updateUnreadCounts(for: webFeeds) { if let updatedArticles = updatedArticles, !updatedArticles.isEmpty {
NotificationCenter.default.post(name: .DownloadArticlesDidUpdateUnreadCounts, object: self, userInfo: nil) shouldSendNotification = true
userInfo[UserInfoKey.updatedArticles] = updatedArticles
}
if shouldSendNotification {
userInfo[UserInfoKey.webFeeds] = webFeeds
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
} }
userInfo[UserInfoKey.newArticles] = newArticles
} }
if !updatedArticles.isEmpty { switch updateArticlesResult {
userInfo[UserInfoKey.updatedArticles] = updatedArticles case .success(let newAndUpdatedArticles):
sendNotificationAbout(newArticles: newAndUpdatedArticles.newArticles, updatedArticles: newAndUpdatedArticles.updatedArticles)
completion(nil)
case .failure(let databaseError):
completion(databaseError)
} }
userInfo[UserInfoKey.webFeeds] = webFeeds
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
completion(possibleError)
} }
} }
@discardableResult @discardableResult

View File

@ -184,8 +184,8 @@ public final class ArticlesDatabase {
// MARK: - Saving and Updating Articles // MARK: - Saving and Updating Articles
/// Update articles and save new ones. /// Update articles and save new ones.
public func update(webFeedID: String, items: Set<ParsedItem>, defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) { public func update(webFeedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
articlesTable.update(webFeedID, items, defaultRead, completion) articlesTable.update(webFeedIDsAndItems, defaultRead, completion)
} }
// MARK: - Status // MARK: - Status

View File

@ -169,8 +169,8 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Updating // MARK: - Updating
func update(_ webFeedID: String, _ items: Set<ParsedItem>, _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) { func update(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
if items.isEmpty { if webFeedIDsAndItems.isEmpty {
callUpdateArticlesCompletionBlock(nil, nil, completion) callUpdateArticlesCompletionBlock(nil, nil, completion)
return return
} }
@ -187,11 +187,15 @@ final class ArticlesTable: DatabaseTable {
self.queue.runInTransaction { (databaseResult) in self.queue.runInTransaction { (databaseResult) in
func makeDatabaseCalls(_ database: FMDatabase) { func makeDatabaseCalls(_ database: FMDatabase) {
let articleIDs = items.articleIDs() var articleIDs = Set<String>()
for (_, parsedItems) in webFeedIDsAndItems {
articleIDs.formUnion(parsedItems.articleIDs())
}
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1 let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
assert(statusesDictionary.count == articleIDs.count) assert(statusesDictionary.count == articleIDs.count)
let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedID, items, self.accountID, statusesDictionary) //2 let allIncomingArticles = Article.articlesWithWebFeedIDsAndItems(webFeedIDsAndItems, self.accountID, statusesDictionary) //2
if allIncomingArticles.isEmpty { if allIncomingArticles.isEmpty {
self.callUpdateArticlesCompletionBlock(nil, nil, completion) self.callUpdateArticlesCompletionBlock(nil, nil, completion)
return return

View File

@ -112,9 +112,16 @@ extension Article {
// return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) }) // return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
// } // }
static func articlesWithWebFeedIDsAndItems(_ webFeedID: String, _ items: Set<ParsedItem>, _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> { static func articlesWithWebFeedIDsAndItems(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ accountID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
let feedArticles = Set(items.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: statusesDictionary[$0.articleID]!) }) var feedArticles = Set<Article>()
for (webFeedID, parsedItems) in webFeedIDsAndItems {
for parsedItem in parsedItems {
let status = statusesDictionary[parsedItem.articleID]!
let article = Article(parsedItem: parsedItem, maximumDateAllowed: maximumDateAllowed, accountID: accountID, webFeedID: webFeedID, status: status)
feedArticles.insert(article)
}
}
return feedArticles return feedArticles
} }
} }