diff --git a/Frameworks/Database/ArticlesTable.swift b/Frameworks/Database/ArticlesTable.swift index c41d85b9a..183cfad0d 100644 --- a/Frameworks/Database/ArticlesTable.swift +++ b/Frameworks/Database/ArticlesTable.swift @@ -79,10 +79,10 @@ final class ArticlesTable: DatabaseTable { // MARK: Updating - func update(_ feed: Feed, _ parsedFeed: ParsedFeed, _ completion: @escaping RSVoidCompletionBlock) { + func update(_ feed: Feed, _ parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesWithFeedCompletionBlock) { if parsedFeed.items.isEmpty { - completion() + completion(nil, nil) return } @@ -90,40 +90,51 @@ final class ArticlesTable: DatabaseTable { // 2. Ignore parsedItems that are userDeleted || (!starred and really old) // 3. Fetch all articles for the feed. // 4. Create Articles with parsedItems. - // 5. + // 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. + let feedID = feed.feedID let parsedItemArticleIDs = Set(parsedFeed.items.map { $0.databaseIdentifierWithFeed(feed) }) let parsedItemsDictionary = parsedFeed.itemsDictionary(with: feed) - statusesTable.ensureStatusesForArticleIDs(parsedItemArticleIDs) { + statusesTable.ensureStatusesForArticleIDs(parsedItemArticleIDs) { // 1 - let filteredParsedItems = self.filterParsedItems(parsedItemsDictionary) + let filteredParsedItems = self.filterParsedItems(parsedItemsDictionary) // 2 if filteredParsedItems.isEmpty { - completion() + completion(nil, nil) return } - queue.fetch{ (database) in - - let fetchedArticles = self.fetchArticlesForFeedID(feedID, withLimits: false, database: database) - - let incomingArticles = Article.articlesWithParsedItems(parsedFeed.items, accountID, feedID) + queue.update{ (database) in + let fetchedArticles = self.fetchArticlesForFeedID(feedID, withLimits: false, database: database) //3 + let fetchedArticlesDictionary = fetchedArticles.dictionary() + let incomingArticles = Article.articlesWithParsedItems(filteredParsedItems, accountID, feedID) //4 + let incomingArticlesDictionary = incomingArticles.dictionary() + let newArticles = Set(incomingArticles.filter { fetchedArticles[$0.articleID] == nil }) //5 + if !newArticles.isEmpty { + saveNewArticles(newArticles, database) + } + + let updatedArticles = incomingArticles.filter{ (incomingArticle) -> Bool in //6 + if let existingArticle = fetchedArticles[incomingArticle.articleID] { + if existingArticle != incomingArticle { + return true + } + } + return false + } + if !updatedArticles.isEmpty { + saveUpdatedArticles(Set(updatedArticles), fetchedArticlesDictionary, database) + } + + DispatchQueue.main.async { + completion(newArticles, updatedArticles) //7 + } } - - - } - - - // 3. For each parsedItem: - // - if userDeleted || (!starred && status.dateArrived < cutoff), then ignore - // - if matches existing article, then update database with changes between the two - // - if new, create article and save in database - - fetchArticlesAsync(feed, withLimits: false) { (articles) in - self.updateArticles(articles.dictionary(), parsedFeed.itemsDictionary(with: feed), feed, completion) } } @@ -182,21 +193,21 @@ private extension ArticlesTable { // MARK: Fetching - func attachRelatedObjects(_ articles: Set
, _ database: FMDatabase) { - - let articleArray = articles.map { $0 as DatabaseObject } - - authorsLookupTable.attachRelatedObjects(to: articleArray, in: database) - attachmentsLookupTable.attachRelatedObjects(to: articleArray, in: database) - tagsLookupTable.attachRelatedObjects(to: articleArray, in: database) - - // In theory, it’s impossible to have a fetched article without a status. - // Let’s handle that impossibility anyway. - // Remember that, if nothing else, the user can edit the SQLite database, - // and thus could delete all their statuses. - - statusesTable.ensureStatusesForArticles(articles, database) - } +// func attachRelatedObjects(_ articles: Set
, _ database: FMDatabase) { +// +// let articleArray = articles.map { $0 as DatabaseObject } +// +// authorsLookupTable.attachRelatedObjects(to: articleArray, in: database) +// attachmentsLookupTable.attachRelatedObjects(to: articleArray, in: database) +// tagsLookupTable.attachRelatedObjects(to: articleArray, in: database) +// +// // In theory, it’s impossible to have a fetched article without a status. +// // Let’s handle that impossibility anyway. +// // Remember that, if nothing else, the user can edit the SQLite database, +// // and thus could delete all their statuses. +// +// statusesTable.ensureStatusesForArticles(articles, database) +// } func articleWithRow(_ row: FMResultSet) -> Article? { diff --git a/Frameworks/Database/Constants.swift b/Frameworks/Database/Constants.swift index a85a78b4a..b3f81cd94 100644 --- a/Frameworks/Database/Constants.swift +++ b/Frameworks/Database/Constants.swift @@ -8,7 +8,24 @@ import Foundation -public struct DatabaseTableName { +// MARK: - Notifications + +public extension Notification.Name { + + public static let ArticlesDidSave = Notification.Name(rawValue: "ArticlesDidSave") +} + +public struct DatabaseNotificationKey { + + // userInfo keys (with Set
values) for ArticlesDidSave. + // One or both will be present. If present, may be empty. + static let newArticles = "newArticles" + static let updatedArticles = "updatedArticles" +} + +// MARK: - Database structure + +struct DatabaseTableName { static let articles = "articles" static let authors = "authors" @@ -19,7 +36,7 @@ public struct DatabaseTableName { static let attachmentsLookup = "attachmentsLookup" } -public struct DatabaseKey { +struct DatabaseKey { // Shared static let databaseID = "databaseID" @@ -65,7 +82,7 @@ public struct DatabaseKey { static let emailAddress = "emailAddress" } -public struct RelationshipName { +struct RelationshipName { static let authors = "authors" static let tags = "tags" diff --git a/Frameworks/Database/Database.swift b/Frameworks/Database/Database.swift index 3ea01febd..f06f61558 100644 --- a/Frameworks/Database/Database.swift +++ b/Frameworks/Database/Database.swift @@ -15,6 +15,7 @@ import Data public typealias ArticleResultBlock = (Set
) -> Void public typealias UnreadCountTable = [String: Int] // feedID: unreadCount public typealias UnreadCountCompletionBlock = (UnreadCountTable) -> Void //feedID: unreadCount +typealias UpdateArticlesWithFeedCompletionBlock = (Set
, Set
) -> Void public final class Database { diff --git a/Frameworks/Database/Extensions/Article+Database.swift b/Frameworks/Database/Extensions/Article+Database.swift index 9191f2cab..a237aa910 100644 --- a/Frameworks/Database/Extensions/Article+Database.swift +++ b/Frameworks/Database/Extensions/Article+Database.swift @@ -73,7 +73,7 @@ extension Article { static func articlesWithParsedItems(_ parsedItems: [ParsedItem], _ accountID: String, _ feedID: String) -> Set
{ - return parsedItems.map{ Article(parsedItem: $0, accountID: accountID, feedID: feedID) } + return Set(parsedItems.map{ Article(parsedItem: $0, accountID: accountID, feedID: feedID) }) } // MARK: Updating with ParsedItem diff --git a/ToDo.ooutline b/ToDo.ooutline index cdd681035..a095913db 100644 Binary files a/ToDo.ooutline and b/ToDo.ooutline differ