diff --git a/Frameworks/Database/ArticlesTable.swift b/Frameworks/Database/ArticlesTable.swift index 2d48e5835..0531631b4 100644 --- a/Frameworks/Database/ArticlesTable.swift +++ b/Frameworks/Database/ArticlesTable.swift @@ -293,7 +293,7 @@ private extension ArticlesTable { // MARK: Update Existing Articles - func articlesWithRelatedObjectChanges(_ comparisonKeyPath: Keypath>, _ updatedArticles: Set
, _ fetchedArticles: [String: Article]) -> Set
{ + func articlesWithRelatedObjectChanges(_ comparisonKeyPath: KeyPath>, _ updatedArticles: Set
, _ fetchedArticles: [String: Article]) -> Set
{ return updatedArticles.filter{ (updatedArticle) -> Bool in if let fetchedArticle = fetchedArticles[updatedArticle.articleID] { @@ -304,7 +304,7 @@ private extension ArticlesTable { } } - func updateRelatedObjects(_ comparisonKeyPath: Keypath>, _ updatedArticles: Set
, _ fetchedArticles: [String: Article], _ lookupTable: DatabaseLookupTable, _ database: FMDatabase) { + func updateRelatedObjects(_ comparisonKeyPath: KeyPath>, _ updatedArticles: Set
, _ fetchedArticles: [String: Article], _ lookupTable: DatabaseLookupTable, _ database: FMDatabase) { let articlesWithChanges = articlesWithRelatedObjectChanges(comparisonKeyPath, updatedArticles, fetchedArticles) if !articlesWithChanges.isEmpty { @@ -312,7 +312,7 @@ private extension ArticlesTable { } } - func saveUpdatedRelatedObjects(_ updatedArticles: Set
, _fetchedArticles: [String: Article], _ database: FMDatabase) { + func saveUpdatedRelatedObjects(_ updatedArticles: Set
, _ fetchedArticles: [String: Article], _ database: FMDatabase) { updateRelatedObjects(\Article.tags, updatedArticles, fetchedArticles, tagsLookupTable, database) updateRelatedObjects(\Article.authors, updatedArticles, fetchedArticles, authorsLookupTable, database) diff --git a/Frameworks/Database/AuthorsTable.swift b/Frameworks/Database/AuthorsTable.swift index 69fe7e135..e867b644e 100644 --- a/Frameworks/Database/AuthorsTable.swift +++ b/Frameworks/Database/AuthorsTable.swift @@ -31,7 +31,7 @@ final class AuthorsTable: DatabaseRelatedObjectsTable { func objectWithRow(_ row: FMResultSet) -> DatabaseObject? { - if let author = authorWithRow(row) { + if let author = Author.authorWithRow(row) { return author as DatabaseObject } return nil @@ -42,23 +42,3 @@ final class AuthorsTable: DatabaseRelatedObjectsTable { } } -private extension AuthorsTable { - - func authorWithRow(_ row: FMResultSet) -> Author? { - - guard let authorID = row.string(forColumn: DatabaseKey.authorID) else { - return nil - } - - if let cachedAuthor = Author.cachedAuthor[authorID] { - return cachedAuthor - } - - guard let author = Author(authorID: authorID, row: row) else { - return nil - } - - cache[authorID] = author - return author - } -} diff --git a/Frameworks/Database/Extensions/Article+Database.swift b/Frameworks/Database/Extensions/Article+Database.swift index 20440a4e9..f25ce50c7 100644 --- a/Frameworks/Database/Extensions/Article+Database.swift +++ b/Frameworks/Database/Extensions/Article+Database.swift @@ -35,7 +35,7 @@ extension Article { let dateModified = row.date(forColumn: DatabaseKey.dateModified) let accountInfo: [String: Any]? = nil // TODO - self.init(account: account, articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: tags, attachments: attachments, accountInfo: accountInfo) + self.init(accountID: accountID, articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: tags, attachments: attachments, accountInfo: accountInfo) } init(parsedItem: ParsedItem, accountID: String, feedID: String) { @@ -44,7 +44,7 @@ extension Article { let attachments = Attachment.attachmentsWithParsedAttachments(parsedItem.attachments) let tags = tagSetWithParsedTags(parsedItem.tags) - self.init(account: account, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: parsedItem.datePublished, dateModified: parsedItem.dateModified, authors: authors, tags: tags, attachments: attachments, accountInfo: nil) + self.init(accountID: accountID, articleID: parsedItem.syncServiceID, feedID: feedID, uniqueID: parsedItem.uniqueID, title: parsedItem.title, contentHTML: parsedItem.contentHTML, contentText: parsedItem.contentText, url: parsedItem.url, externalURL: parsedItem.externalURL, summary: parsedItem.summary, imageURL: parsedItem.imageURL, bannerImageURL: parsedItem.bannerImageURL, datePublished: parsedItem.datePublished, dateModified: parsedItem.dateModified, authors: authors, tags: tags, attachments: attachments, accountInfo: nil) } func databaseDictionary() -> NSDictionary { @@ -72,7 +72,7 @@ extension Article { return d.copy() as! NSDictionary } - private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath, _ otherArticle: Article, _ key: String, _ dictionary: NSMutableDictionary) { + private func addPossibleStringChangeWithKeyPath(_ comparisonKeyPath: KeyPath, _ otherArticle: Article, _ key: String, _ dictionary: NSMutableDictionary) { if self[keyPath: comparisonKeyPath] != otherArticle[keyPath: comparisonKeyPath] { dictionary.addOptionalStringDefaultingEmpty(self[keyPath: comparisonKeyPath], key) @@ -86,8 +86,13 @@ extension Article { } let d = NSMutableDictionary() + if uniqueID != otherArticle.uniqueID { + // This should be super-rare, if ever. + if !otherArticle.uniqueID.isEmpty { + d[DatabaseKey.uniqueID] = otherArticle.uniqueID + } + } - 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) @@ -100,12 +105,12 @@ extension Article { // If updated versions of dates are nil, and we have existing dates, keep the existing dates. // This is data that’s good to have, and it’s likely that a feed removing dates is doing so in error. - if article.datePublished != otherArticle.datePublished { + if datePublished != otherArticle.datePublished { if let updatedDatePublished = otherArticle.datePublished { d[DatabaseKey.datePublished] = updatedDatePublished } } - if article.dateModified != otherArticle.dateModified { + if dateModified != otherArticle.dateModified { if let updatedDateModified = otherArticle.dateModified { d[DatabaseKey.dateModified] = updatedDateModified } @@ -113,7 +118,7 @@ extension Article { // TODO: accountInfo - if d.isEmpty { + if d.count < 1 { return nil } @@ -143,26 +148,6 @@ extension Set where Element == Article { return Set(map { $0.databaseID }) } - func eachHasAStatus() -> Bool { - - for article in self { - if article.status == nil { - return false - } - } - return true - } - - func missingStatuses() -> Set
{ - - return Set
(self.filter { $0.status == nil }) - } - - func statuses() -> Set { - - return Set(self.flatMap { $0.status }) - } - func dictionary() -> [String: Article] { var d = [String: Article]() diff --git a/Frameworks/Database/Extensions/Author+Database.swift b/Frameworks/Database/Extensions/Author+Database.swift index 4a4a1ddc7..452d000ad 100644 --- a/Frameworks/Database/Extensions/Author+Database.swift +++ b/Frameworks/Database/Extensions/Author+Database.swift @@ -13,32 +13,39 @@ import RSParser extension Author { - init?(authorID: String, row: FMResultSet) { - - let name = row.string(forColumn: DatabaseKey.name) - let url = row.string(forColumn: DatabaseKey.url) - let avatarURL = row.string(forColumn: DatabaseKey.avatarURL) - let emailAddress = row.string(forColumn: DatabaseKey.emailAddress) - - self.init(authorID: authorID, name: name, url: url, avatarURL: avatarURL, emailAddress: emailAddress) - } - - init?(parsedAuthor: ParsedAuthor) { - - self.init(authorID: nil, name: parsedAuthor.name, url: parsedAuthor.url, avatarURL: parsedAuthor.avatarURL, emailAddress: parsedAuthor.emailAddress) - } - static func authorsWithParsedAuthors(_ parsedAuthors: [ParsedAuthor]?) -> Set? { + assert(!Thread.isMainThread) + guard let parsedAuthors = parsedAuthors else { return nil } - let authors = Set(parsedAuthors.flatMap { Author(parsedAuthor: $0) }) + let authors = Set(parsedAuthors.flatMap { authorWithParsedAuthor($0) }) return authors.isEmpty ? nil : authors } + + static func authorWithRow(_ row: FMResultSet) -> Author? { + + guard let authorID = row.string(forColumn: DatabaseKey.authorID) else { + return nil + } + + if let cachedAuthor = cachedAuthor(authorID) { + return cachedAuthor + } + + guard let author = Author(authorID: authorID, row: row) else { + return nil + } + + cacheAuthor(author) + return author + } } +// MARK: - DatabaseObject + extension Author: DatabaseObject { public var databaseID: String { @@ -47,3 +54,55 @@ extension Author: DatabaseObject { } } } + +// MARK: - Private + +private extension Author { + + init?(authorID: String, row: FMResultSet) { + + let name = row.string(forColumn: DatabaseKey.name) + let url = row.string(forColumn: DatabaseKey.url) + let avatarURL = row.string(forColumn: DatabaseKey.avatarURL) + let emailAddress = row.string(forColumn: DatabaseKey.emailAddress) + + self.init(authorID: authorID, name: name, url: url, avatarURL: avatarURL, emailAddress: emailAddress) + } + + init?(parsedAuthor: ParsedAuthor) { + + self.init(authorID: nil, name: parsedAuthor.name, url: parsedAuthor.url, avatarURL: parsedAuthor.avatarURL, emailAddress: parsedAuthor.emailAddress) + } + + static func authorWithParsedAuthor(_ parsedAuthor: ParsedAuthor) -> Author? { + + if let author = Author(parsedAuthor: parsedAuthor) { + if let authorFromCache = cachedAuthor(author.authorID) { + return authorFromCache + } + cacheAuthor(author) + return author + } + + return nil + } + + // The authorCache isn’t because we need uniquing — it’s just to cut down + // on the number of Author instances, since they would be frequently duplicated. + // (That is, a given feed might have 10 or 20 or whatever of the same Author.) + + private static var authorCache = [String: Author]() //queue-only + + static func cachedAuthor(_ authorID: String) -> Author? { + + assert(!Thread.isMainThread) + return authorCache[authorID] + } + + static func cacheAuthor(_ author: Author) { + + assert(!Thread.isMainThread) + authorCache[author.authorID] = author + } +} + diff --git a/Frameworks/Database/StatusesTable.swift b/Frameworks/Database/StatusesTable.swift index 2efbcd925..64905361a 100644 --- a/Frameworks/Database/StatusesTable.swift +++ b/Frameworks/Database/StatusesTable.swift @@ -169,7 +169,7 @@ private final class StatusCache { } } - subscript(_ articleID: String) -> ArticleStatus { + subscript(_ articleID: String) -> ArticleStatus? { get { return self[articleID] } diff --git a/ToDo.ooutline b/ToDo.ooutline index 1718e4fa0..13a6de0ef 100644 Binary files a/ToDo.ooutline and b/ToDo.ooutline differ