mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2025-01-28 01:39:47 +01:00
When syncing, update the database by articleIDs rather than by feeds — this means *far* fewer fetches and much less data pulled from the database. It should help app responsiveness dramatically during sync, and especially during an initial sync.
This commit is contained in:
parent
099172d9d2
commit
1d0cacd5fc
@ -573,12 +573,40 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping (() -> Void)) {
|
||||
// Used only by an On My Mac account.
|
||||
feed.takeSettings(from: parsedFeed)
|
||||
update(feed, parsedItems: parsedFeed.items, completion)
|
||||
let feedIDsAndItems = [feed.feedID: parsedFeed.items]
|
||||
update(feedIDsAndItems: feedIDsAndItems, defaultRead: false, completion: completion)
|
||||
}
|
||||
|
||||
|
||||
func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping (() -> Void)) {
|
||||
guard !feedIDsAndItems.isEmpty else {
|
||||
completion()
|
||||
return
|
||||
}
|
||||
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
|
||||
var userInfo = [String: Any]()
|
||||
let feeds = Set(feedIDsAndItems.compactMap { (key, _) -> Feed? in
|
||||
self.existingFeed(withFeedID: key)
|
||||
})
|
||||
if let newArticles = newArticles, !newArticles.isEmpty {
|
||||
self.updateUnreadCounts(for: feeds)
|
||||
userInfo[UserInfoKey.newArticles] = newArticles
|
||||
}
|
||||
if let updatedArticles = updatedArticles, !updatedArticles.isEmpty {
|
||||
userInfo[UserInfoKey.updatedArticles] = updatedArticles
|
||||
}
|
||||
userInfo[UserInfoKey.feeds] = feeds
|
||||
|
||||
completion()
|
||||
|
||||
NotificationCenter.default.post(name: .AccountDidDownloadArticles, object: self, userInfo: userInfo)
|
||||
}
|
||||
}
|
||||
|
||||
func update(_ feed: Feed, parsedItems: Set<ParsedItem>, defaultRead: Bool = false, _ completion: @escaping (() -> Void)) {
|
||||
database.update(feedID: feed.feedID, parsedItems: parsedItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
|
||||
let feedIDsAndItems = [feed.feedID: parsedItems]
|
||||
database.update(feedIDsAndItems: feedIDsAndItems, defaultRead: defaultRead) { (newArticles, updatedArticles) in
|
||||
var userInfo = [String: Any]()
|
||||
if let newArticles = newArticles, !newArticles.isEmpty {
|
||||
self.updateUnreadCounts(for: Set([feed]))
|
||||
|
@ -1054,7 +1054,6 @@ private extension FeedbinAccountDelegate {
|
||||
}
|
||||
|
||||
func refreshArticles(_ account: Account, page: String?, completion: @escaping (() -> Void)) {
|
||||
|
||||
guard let page = page else {
|
||||
completion()
|
||||
return
|
||||
@ -1074,42 +1073,16 @@ private extension FeedbinAccountDelegate {
|
||||
os_log(.error, log: self.log, "Refresh articles for additional pages failed: %@.", error.localizedDescription)
|
||||
completion()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func processEntries(account: Account, entries: [FeedbinEntry]?, completion: @escaping (() -> Void)) {
|
||||
|
||||
let parsedItems = mapEntriesToParsedItems(entries: entries)
|
||||
let parsedMap = Dictionary(grouping: parsedItems, by: { item in item.feedURL } )
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
for (feedID, mapItems) in parsedMap {
|
||||
|
||||
group.enter()
|
||||
|
||||
if let feed = account.existingFeed(withFeedID: feedID) {
|
||||
DispatchQueue.main.async {
|
||||
account.update(feed, parsedItems: Set(mapItems), defaultRead: true) {
|
||||
group.leave()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
group.leave()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
group.notify(queue: DispatchQueue.main) {
|
||||
completion()
|
||||
}
|
||||
|
||||
let feedIDsAndItems = Dictionary(grouping: parsedItems, by: { item in item.feedURL } ).mapValues { Set($0) }
|
||||
account.update(feedIDsAndItems: feedIDsAndItems, defaultRead: true, completion: completion)
|
||||
}
|
||||
|
||||
func mapEntriesToParsedItems(entries: [FeedbinEntry]?) -> Set<ParsedItem> {
|
||||
|
||||
guard let entries = entries else {
|
||||
return Set<ParsedItem>()
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ import Articles
|
||||
|
||||
public typealias UnreadCountDictionary = [String: Int] // feedID: unreadCount
|
||||
public typealias UnreadCountCompletionBlock = (UnreadCountDictionary) -> Void
|
||||
public typealias UpdateArticlesWithFeedCompletionBlock = (Set<Article>?, Set<Article>?) -> Void //newArticles, updatedArticles
|
||||
public typealias UpdateArticlesCompletionBlock = (Set<Article>?, Set<Article>?) -> Void //newArticles, updatedArticles
|
||||
|
||||
public final class ArticlesDatabase {
|
||||
|
||||
@ -118,10 +118,11 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Saving and Updating Articles
|
||||
|
||||
public func update(feedID: String, parsedItems: Set<ParsedItem>, defaultRead: Bool, completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
|
||||
return articlesTable.update(feedID, parsedItems, defaultRead, completion)
|
||||
/// Update articles and save new ones. The key for feedIDsAndItems is feedID.
|
||||
public func update(feedIDsAndItems: [String: Set<ParsedItem>], defaultRead: Bool, completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
articlesTable.update(feedIDsAndItems, defaultRead, completion)
|
||||
}
|
||||
|
||||
|
||||
public func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool) {
|
||||
articlesTable.ensureStatuses(articleIDs, defaultRead, statusKey, flag)
|
||||
}
|
||||
|
@ -208,9 +208,9 @@ final class ArticlesTable: DatabaseTable {
|
||||
}
|
||||
|
||||
// MARK: - Updating
|
||||
|
||||
func update(_ feedID: String, _ parsedItems: Set<ParsedItem>, _ read: Bool, _ completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
|
||||
if parsedItems.isEmpty {
|
||||
|
||||
func update(_ feedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
if feedIDsAndItems.isEmpty {
|
||||
completion(nil, nil)
|
||||
return
|
||||
}
|
||||
@ -224,30 +224,34 @@ final class ArticlesTable: DatabaseTable {
|
||||
// 7. Call back with new and updated Articles.
|
||||
// 8. Update search index.
|
||||
|
||||
let articleIDs = Set(parsedItems.map { $0.articleID })
|
||||
|
||||
var articleIDs = Set<String>()
|
||||
for (_, parsedItems) in feedIDsAndItems {
|
||||
articleIDs.formUnion(parsedItems.articleIDs())
|
||||
}
|
||||
|
||||
self.queue.update { (database) in
|
||||
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
|
||||
assert(statusesDictionary.count == articleIDs.count)
|
||||
|
||||
let allIncomingArticles = Article.articlesWithParsedItems(parsedItems, self.accountID, feedID, statusesDictionary) //2
|
||||
|
||||
let allIncomingArticles = Article.articlesWithFeedIDsAndItems(feedIDsAndItems, self.accountID, statusesDictionary) //2
|
||||
if allIncomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3
|
||||
if incomingArticles.isEmpty {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
let fetchedArticles = self.fetchArticlesForFeedID(feedID, withLimits: false, database) //4
|
||||
let incomingArticleIDs = incomingArticles.articleIDs()
|
||||
let fetchedArticles = self.fetchArticles(articleIDs: incomingArticleIDs, database) //4
|
||||
let fetchedArticlesDictionary = fetchedArticles.dictionary()
|
||||
|
||||
|
||||
let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
|
||||
let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6
|
||||
|
||||
|
||||
self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7
|
||||
|
||||
// 8. Update search index.
|
||||
@ -258,16 +262,75 @@ final class ArticlesTable: DatabaseTable {
|
||||
if let updatedArticles = updatedArticles {
|
||||
articlesToIndex.formUnion(updatedArticles)
|
||||
}
|
||||
let articleIDs = articlesToIndex.articleIDs()
|
||||
if articleIDs.isEmpty {
|
||||
let articleIDsToIndex = articlesToIndex.articleIDs()
|
||||
if articleIDsToIndex.isEmpty {
|
||||
return
|
||||
}
|
||||
DispatchQueue.main.async {
|
||||
self.searchTable.ensureIndexedArticles(for: articleIDs)
|
||||
self.searchTable.ensureIndexedArticles(for: articleIDsToIndex)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// func update(_ feedID: String, _ parsedItems: Set<ParsedItem>, _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
// if parsedItems.isEmpty {
|
||||
// completion(nil, nil)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// // 1. Ensure statuses for all the incoming articles.
|
||||
// // 2. Create incoming articles with parsedItems.
|
||||
// // 3. Ignore incoming articles that are userDeleted || (!starred and really old)
|
||||
// // 4. Fetch all articles for the feed.
|
||||
// // 5. Create array of Articles not in database and save them.
|
||||
// // 6. Create array of updated Articles and save what’s changed.
|
||||
// // 7. Call back with new and updated Articles.
|
||||
// // 8. Update search index.
|
||||
//
|
||||
// let articleIDs = Set(parsedItems.map { $0.articleID })
|
||||
//
|
||||
// self.queue.update { (database) in
|
||||
// let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
|
||||
// assert(statusesDictionary.count == articleIDs.count)
|
||||
//
|
||||
// let allIncomingArticles = Article.articlesWithParsedItems(parsedItems, self.accountID, feedID, statusesDictionary) //2
|
||||
// if allIncomingArticles.isEmpty {
|
||||
// self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let incomingArticles = self.filterIncomingArticles(allIncomingArticles) //3
|
||||
// if incomingArticles.isEmpty {
|
||||
// self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
// return
|
||||
// }
|
||||
//
|
||||
// let fetchedArticles = self.fetchArticlesForFeedID(feedID, withLimits: false, database) //4
|
||||
// let fetchedArticlesDictionary = fetchedArticles.dictionary()
|
||||
//
|
||||
// let newArticles = self.findAndSaveNewArticles(incomingArticles, fetchedArticlesDictionary, database) //5
|
||||
// let updatedArticles = self.findAndSaveUpdatedArticles(incomingArticles, fetchedArticlesDictionary, database) //6
|
||||
//
|
||||
// self.callUpdateArticlesCompletionBlock(newArticles, updatedArticles, completion) //7
|
||||
//
|
||||
// // 8. Update search index.
|
||||
// var articlesToIndex = Set<Article>()
|
||||
// if let newArticles = newArticles {
|
||||
// articlesToIndex.formUnion(newArticles)
|
||||
// }
|
||||
// if let updatedArticles = updatedArticles {
|
||||
// articlesToIndex.formUnion(updatedArticles)
|
||||
// }
|
||||
// let articleIDs = articlesToIndex.articleIDs()
|
||||
// if articleIDs.isEmpty {
|
||||
// return
|
||||
// }
|
||||
// DispatchQueue.main.async {
|
||||
// self.searchTable.ensureIndexedArticles(for: articleIDs)
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool) {
|
||||
self.queue.update { (database) in
|
||||
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, defaultRead, database)
|
||||
@ -596,7 +659,7 @@ private extension ArticlesTable {
|
||||
|
||||
// MARK: - Saving Parsed Items
|
||||
|
||||
func callUpdateArticlesCompletionBlock(_ newArticles: Set<Article>?, _ updatedArticles: Set<Article>?, _ completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
|
||||
func callUpdateArticlesCompletionBlock(_ newArticles: Set<Article>?, _ updatedArticles: Set<Article>?, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
DispatchQueue.main.async {
|
||||
completion(newArticles, updatedArticles)
|
||||
}
|
||||
@ -728,3 +791,8 @@ private extension ArticlesTable {
|
||||
}
|
||||
}
|
||||
|
||||
private extension Set where Element == ParsedItem {
|
||||
func articleIDs() -> Set<String> {
|
||||
return Set<String>(map { $0.articleID })
|
||||
}
|
||||
}
|
||||
|
@ -79,11 +79,20 @@ extension Article {
|
||||
return d.count < 1 ? nil : d
|
||||
}
|
||||
|
||||
static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ accountID: String, _ feedID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||
// static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ accountID: String, _ feedID: String, _ statusesDictionary: [String: ArticleStatus]) -> Set<Article> {
|
||||
// let maximumDateAllowed = Date().addingTimeInterval(60 * 60 * 24) // Allow dates up to about 24 hours ahead of now
|
||||
// return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
||||
// }
|
||||
|
||||
static func articlesWithFeedIDsAndItems(_ feedIDsAndItems: [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
|
||||
return Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
||||
var articles = Set<Article>()
|
||||
for (feedID, parsedItems) in feedIDsAndItems {
|
||||
let feedArticles = Set(parsedItems.map{ Article(parsedItem: $0, maximumDateAllowed: maximumDateAllowed, accountID: accountID, feedID: feedID, status: statusesDictionary[$0.articleID]!) })
|
||||
articles.formUnion(feedArticles)
|
||||
}
|
||||
return articles
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension Article: DatabaseObject {
|
||||
|
Loading…
x
Reference in New Issue
Block a user