Add article.changesFrom() to get changes in an Article.

This commit is contained in:
Brent Simmons 2017-09-09 12:09:48 -07:00
parent 54b5100b09
commit 3c47b23b8b
3 changed files with 89 additions and 97 deletions

View File

@ -442,20 +442,34 @@ private extension ArticlesTable {
saveUpdatedRelatedObjects(updatedArticles, fetchedArticles, database)
for updatedArticle in updatedArticles {
guard let fetchedArticle = fetchedArticle[updatedArticle.articleID] else {
assertionFailure("Expected to find matching fetched article.");
}
if let changesDictionary = updatedArticle.changesFrom(fetchedArticles[upd])
}
}
func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ feed: Feed) -> Set<Article> {
assert(!Thread.isMainThread)
let feedID = feed.feedID
return Set(parsedItems.flatMap{ articleWithParsedItem($0, feedID) })
func saveUpdatedArticle(_ updatedArticle: Article, _ fetchedArticles: [String: Article], _ database: FMDatabase) {
// Only update exactly what has changed in the Article (if anything).
// Untested theory: this gets us better performance and less database fragmentation.
guard let fetchedArticle = fetchedArticle[updatedArticle.articleID] else {
assertionFailure("Expected to find matching fetched article.");
saveNewArticles(Set([updatedArticle]), database)
return
}
guard let changesDictionary = updatedArticle.changesFrom(fetchedArticle), !changesDictionary.isEmpty else {
// Not unexpected. There may be no changes.
return
}
updateRowsWithDictionary(changesDictionary, whereKey: DatabaseKey.articleID, matches: updatedArticle.articleID, database: database)
}
func articleWithParsedItem(_ parsedItem: ParsedItem, _ feedID: String) -> Article? {
return Article(parsedItem: parsedItem, feedID: feedID, accountID: accountID)
}
func statusIndicatesArticleIsIgnorable(_ status: ArticleStatus) -> Bool {
// Ignorable articles: either userDeleted==1 or (not starred and arrival date > 4 months).

View File

@ -57,6 +57,7 @@ extension Article {
d.addOptionalString(title, DatabaseKey.title)
d.addOptionalString(contentHTML, DatabaseKey.contentHTML)
d.addOptionalString(contentText, DatabaseKey.contentText)
d.addOptionalString(url, DatabaseKey.url)
d.addOptionalString(externalURL, DatabaseKey.externalURL)
d.addOptionalString(summary, DatabaseKey.summary)
@ -70,98 +71,60 @@ extension Article {
return d.copy() as! NSDictionary
}
private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath<Article,String>, _ otherArticle: Article, _ key: String, _ dictionary: NSMutableDictionary) {
if self[keyPath: comparisonKeyPath] != otherArticle[keyPath: comparisonKeyPath] {
dictionary.addOptionalStringDefaultingEmpty(self[keyPath: comparisonKeyPath], key)
}
}
func changesFrom(_ otherArticle: Article) -> NSDictionary? {
if self == otherArticle {
return nil
}
let d = NSMutableDictionary()
addPossibleStringChangeWithKeyPath(\Article.uniqueID, otherArticle, DatabaseKey.uniqueID, d)
addPossibleStringChangeWithKeyPath(\Article.title, otherArticle, DatabaseKey.title, d)
addPossibleStringChangeWithKeyPath(\Article.contentHTML, otherArticle, DatabaseKey.contentHTML, d)
addPossibleStringChangeWithKeyPath(\Article.contentText, otherArticle, DatabaseKey.contentText, d)
addPossibleStringChangeWithKeyPath(\Article.url, otherArticle, DatabaseKey.url, d)
addPossibleStringChangeWithKeyPath(\Article.externalURL, otherArticle, DatabaseKey.externalURL, d)
addPossibleStringChangeWithKeyPath(\Article.summary, otherArticle, DatabaseKey.summary, d)
addPossibleStringChangeWithKeyPath(\Article.imageURL, otherArticle, DatabaseKey.imageURL, d)
addPossibleStringChangeWithKeyPath(\Article.bannerImageURL, otherArticle, DatabaseKey.bannerImageURL, d)
// If updated versions of dates are nil, and we have existing dates, keep the existing dates.
// This is data thats good to have, and its likely that a feed removing dates is doing so in error.
if article.datePublished != otherArticle.datePublished {
if let updatedDatePublished = otherArticle.datePublished {
d[DatabaseKey.datePublished] = updatedDatePublished
}
}
if article.dateModified != otherArticle.dateModified {
if let updatedDateModified = otherArticle.dateModified {
d[DatabaseKey.dateModified] = updatedDateModified
}
}
// TODO: accountInfo
if d.isEmpty {
return nil
}
return d
}
static func articlesWithParsedItems(_ parsedItems: [ParsedItem], _ accountID: String, _ feedID: String) -> Set<Article> {
return Set(parsedItems.map{ Article(parsedItem: $0, accountID: accountID, feedID: feedID) })
}
// MARK: Updating with ParsedItem
// func updateTagsWithParsedTags(_ parsedTags: [String]?) -> Bool {
//
// // Return true if there's a change.
//
// let currentTags = tags
//
// if parsedTags == nil && currentTags == nil {
// return false
// }
// if parsedTags != nil && currentTags == nil {
// tags = Set(parsedItemTags!)
// return true
// }
// if parsedTags == nil && currentTags != nil {
// tags = nil
// return true
// }
// let parsedTagSet = Set(parsedTags!)
// if parsedTagSet == tags! {
// return false
// }
// tags = parsedTagSet
// return true
// }
//
// func updateAttachmentsWithParsedAttachments(_ parsedAttachments: [ParsedAttachment]?) -> Bool {
//
// // Return true if there's a change.
//
// let currentAttachments = attachments
// let updatedAttachments = Attachment.attachmentsWithParsedAttachments(parsedAttachments)
//
// if updatedAttachments == nil && currentAttachments == nil {
// return false
// }
// if updatedAttachments != nil && currentAttachments == nil {
// attachments = updatedAttachments
// return true
// }
// if updatedAttachments == nil && currentAttachments != nil {
// attachments = nil
// return true
// }
//
// guard let currentAttachments = currentAttachments, let updatedAttachments = updatedAttachments else {
// assertionFailure("currentAttachments and updatedAttachments must both be non-nil.")
// return false
// }
// if currentAttachments != updatedAttachments {
// attachments = updatedAttachments
// return true
// }
// return false
// }
//
// func updateAuthorsWithParsedAuthors(_ parsedAuthors: [ParsedAuthor]?) -> Bool {
//
// // Return true if there's a change.
//
// let currentAuthors = authors
// let updatedAuthors = Author.authorsWithParsedAuthors(parsedAuthors)
//
// if updatedAuthors == nil && currentAuthors == nil {
// return false
// }
// if updatedAuthors != nil && currentAuthors == nil {
// authors = updatedAuthors
// return true
// }
// if updatedAuthors == nil && currentAuthors != nil {
// authors = nil
// return true
// }
//
// guard let currentAuthors = currentAuthors, let updatedAuthors = updatedAuthors else {
// assertionFailure("currentAuthors and updatedAuthors must both be non-nil.")
// return false
// }
// if currentAuthors != updatedAuthors {
// authors = updatedAuthors
// return true
// }
// return false
// }
}
extension Article: DatabaseObject {
@ -224,6 +187,16 @@ private extension NSMutableDictionary {
}
}
func addOptionalStringDefaultingEmpty(_ value: String?, _ key: String) {
if let value = value {
self[key] = value
}
else {
self[key] = ""
}
}
func addOptionalDate(_ date: Date?, _ key: String) {
if let date = date {

View File

@ -47,6 +47,11 @@ public extension DatabaseTable {
let _ = database.rs_updateRows(withValue: value, valueKey: valueKey, whereKey: whereKey, inValues: matches, tableName: self.name)
}
public func updateRowsWithDictionary(_ dictionary: NSDictionary, whereKey: String, matches: Any, database: FMDatabase) {
let _ = database.rs_updateRows(with: dictionary as! [AnyHashable : Any], whereKey: whereKey, equalsValue: matches, tableName: self.name)
}
// MARK: Saving
public func insertRows(_ dictionaries: [NSDictionary], insertType: RSDatabaseInsertType, in database: FMDatabase) {