Use temporary article dictionaries when fetching articles.

This commit is contained in:
Brent Simmons 2017-09-20 13:29:21 -07:00
parent f0eea49179
commit 93db92d611
3 changed files with 108 additions and 105 deletions

View File

@ -158,54 +158,74 @@ private extension ArticlesTable {
func articlesWithResultSet(_ resultSet: FMResultSet, _ database: FMDatabase) -> Set<Article> {
// Create set of stub Articles without related objects.
// Then fetch the related objects, given the set of articleIDs.
// Then create set of Articles *with* related objects and return it.
// 1. Create article dictionaries without related objects.
// 2. Then fetch the related objects, given the set of articleIDs.
// 3. Then fetch statuses.
// 4. Then create set of Articles with status and related objects and return it.
// 1. Create article dictionaries.
let articleDictionaries = makeArticleDictionaries(with: resultSet)
if articleDictionaries.isEmpty {
return Set<Article>()
}
// Fetch related objects.
let articleIDs = articleDictionaries.map { $0[DatabaseKey.articleID] }
let articleIDs = Set(articleDictionaries.map { $0[DatabaseKey.articleID] as! String })
// 2. Fetch related objects.
let authorsMap = authorsLookupTable.fetchRelatedObjects(for: articleIDs, in: database)
let attachmentsMap = attachmentsLookupTable.fetchRelatedObjects(for: articleIDs, in: database)
let tagsMap = tagsLookupTable.fetchRelatedObjects(for: articleIDs, in: database)
// TODO: get statusesDictionary from statusesTable
// 3. All statuses for articleDictionaries were created (when needed) in makeArticleDictionaries.
// Create articles with related objects.
let statusesDictionary = statusesTable.statusesDictionary(articleIDs)
assert(statusesDictionary.count == articleIDs.count)
let articles = Set(articleDictionaries.flatMap { articleWithDictionary($0, authorsMap, attachmentsMap, tagsMap) })
return articles
// 4. Create articles with related objects.
let articles = articleDictionaries.flatMap { (articleDictionary) -> Article? in
guard let articleID = articleDictionary[DatabaseKey.articleID] as? String else {
assertionFailure("articleID expected")
return nil
}
guard let status = statusesDictionary[articleID] else {
assertionFailure("status expected")
return nil
}
return articleWithDictionary(articleDictionary, status, authorsMap, attachmentsMap, tagsMap)
}
return Set(articles)
}
func articleWithDictionary(_ articleDictionary: [String: Any], _ authorsMap: RelatedObjectsMap?, _ attachmentsMap: RelatedObjectsMap?, _ tagsMap: RelatedObjectsMap?) -> Article? {
func articleWithDictionary(_ articleDictionary: [String: Any], _ status: ArticleStatus, _ authorsMap: RelatedObjectsMap?, _ attachmentsMap: RelatedObjectsMap?, _ tagsMap: RelatedObjectsMap?) -> Article? {
let articleID = articleDictionary[DatabaseKey.articleID]!
let status = articleDictionary[statusKey]!
let articleID = articleDictionary[DatabaseKey.articleID]! as! String
let authors = authorsMap?.authors(for: articleID)
let attachments = attachmentsMap?.attachments(for: articleID)
let tags = tagsMap?.tags(for: articleID)
return Author(dictionary: articleDictionary, status: status, accountID: accountID, authors: authors, attachments: attachments, tags: tags)
return Article(dictionary: articleDictionary, accountID: accountID, status: status, authors: authors, attachments: attachments, tags: tags)
}
func makeArticleDictionaries(with resultSet: FMResultSet) -> Set<[String: Any]> {
func makeArticleDictionaries(with resultSet: FMResultSet) -> [[String: Any]] {
let dictionaries = resultSet.mapToSet{ (row) -> [String: Any]? in
let dictionaries = resultSet.flatMap{ (row) -> [String: Any]? in
// The resultSet is a result of a JOIN query with the statuses table,
// so we can get the statuses at the same time and avoid additional database lookups.
guard let status = statusesTable.statusWithRow(resultSet) else {
// Dont need status locally we want it to get cached by statusesTable.
guard let _ = statusesTable.statusWithRow(resultSet) else {
assertionFailure("Expected status.")
return nil
}
guard let let articleID = row.string(forColumn: DatabaseKey.articleID) else {
guard let articleID = row.string(forColumn: DatabaseKey.articleID) else {
return nil
}
guard let feedID = row.string(forColumn: DatabaseKey.feedID) else {
@ -217,7 +237,6 @@ private extension ArticlesTable {
var d = [String: Any]()
d[statusKey] = status
d[DatabaseKey.articleID] = articleID
d[DatabaseKey.feedID] = feedID
d[DatabaseKey.uniqueID] = uniqueID
@ -225,19 +244,36 @@ private extension ArticlesTable {
if let title = row.string(forColumn: DatabaseKey.title) {
d[DatabaseKey.title] = title
}
if let contentHTML = row.string(forColumn: DatabaseKey.contentHTML) {
d[DatabaseKey.contentHTML] = contentHTML
}
if let contentText = row.string(forColumn: DatabaseKey.contentText) {
d[DatabaseKey.contentText] = contentText
}
if let url = row.string(forColumn: DatabaseKey.url) {
d[DatabaseKey.url] = url
}
if let externalURL = row.string(forColumn: DatabaseKey.externalURL) {
d[DatabaseKey.externalURL] = externalURL
}
if let summary = row.string(forColumn: DatabaseKey.summary) {
d[DatabaseKey.summary] = summary
}
if let imageURL = row.string(forColumn: DatabaseKey.imageURL) {
d[DatabaseKey.imageURL] = imageURL
}
if let bannerImageURL = row.string(forColumn: DatabaseKey.bannerImageURL) {
d[DatabaseKey.bannerImageURL] = bannerImageURL
}
if let datePublished = row.date(forColumn: DatabaseKey.datePublished) {
d[DatabaseKey.datePublished] = datePublished
}
if let dateModified = row.date(forColumn: DatabaseKey.dateModified) {
d[DatabaseKey.dateModified] = dateModified
}
// TODO: accountInfo
// TODO
// let contentHTML = row.string(forColumn: DatabaseKey.contentHTML)
// let contentText = row.string(forColumn: DatabaseKey.contentText)
// let url = row.string(forColumn: DatabaseKey.url)
// let externalURL = row.string(forColumn: DatabaseKey.externalURL)
// let summary = row.string(forColumn: DatabaseKey.summary)
// let imageURL = row.string(forColumn: DatabaseKey.imageURL)
// let bannerImageURL = row.string(forColumn: DatabaseKey.bannerImageURL)
// let datePublished = row.date(forColumn: DatabaseKey.datePublished)
// let dateModified = row.date(forColumn: DatabaseKey.dateModified)
// let accountInfo: AccountInfo? = nil // TODO
//
return d
}
resultSet.close()
@ -245,39 +281,6 @@ private extension ArticlesTable {
return dictionaries
}
func makeStubArticles(with resultSet: FMResultSet) -> Set<Article> {
var stubArticles = Set<Article>()
// Note: the resultSet is a result of a JOIN query with the statuses table,
// so we can get the statuses at the same time and avoid additional database lookups.
while resultSet.next() {
guard let status = statusesTable.statusWithRow(resultSet) else {
assertionFailure("Expected status.")
continue
}
if let stubArticle = Article(row: resultSet, accountID: accountID, status: status) {
stubArticles.insert(stubArticle)
}
}
resultSet.close()
return stubArticles
}
func articleWithAttachedRelatedObjects(_ stubArticle: Article, _ authorsMap: RelatedObjectsMap?, _ attachmentsMap: RelatedObjectsMap?, _ tagsMap: RelatedObjectsMap?) -> Article {
let articleID = stubArticle.articleID
let authors = authorsMap?.authors(for: articleID)
let attachments = attachmentsMap?.attachments(for: articleID)
let tags = tagsMap?.tags(for: articleID)
let realArticle = stubArticle.articleByAttaching(authors, attachments, tags)
return realArticle
}
func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject], withLimits: Bool) -> Set<Article> {
// Dont fetch articles that shouldnt appear in the UI. The rules:

View File

@ -13,34 +13,34 @@ import RSParser
extension Article {
init?(row: FMResultSet, accountID: String, authors: Set<Author>? = nil, attachments: Set<Attachment>? = nil, tags: Set<String>? = nil, status: ArticleStatus) {
guard let feedID = row.string(forColumn: DatabaseKey.feedID) else {
return nil
}
guard let uniqueID = row.string(forColumn: DatabaseKey.uniqueID) else {
return nil
}
let articleID = row.string(forColumn: DatabaseKey.articleID)!
let title = row.string(forColumn: DatabaseKey.title)
let contentHTML = row.string(forColumn: DatabaseKey.contentHTML)
let contentText = row.string(forColumn: DatabaseKey.contentText)
let url = row.string(forColumn: DatabaseKey.url)
let externalURL = row.string(forColumn: DatabaseKey.externalURL)
let summary = row.string(forColumn: DatabaseKey.summary)
let imageURL = row.string(forColumn: DatabaseKey.imageURL)
let bannerImageURL = row.string(forColumn: DatabaseKey.bannerImageURL)
let datePublished = row.date(forColumn: DatabaseKey.datePublished)
let dateModified = row.date(forColumn: DatabaseKey.dateModified)
let accountInfo: AccountInfo? = nil // TODO
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, status: status)
}
// init?(row: FMResultSet, accountID: String, authors: Set<Author>? = nil, attachments: Set<Attachment>? = nil, tags: Set<String>? = nil, status: ArticleStatus) {
//
// guard let feedID = row.string(forColumn: DatabaseKey.feedID) else {
// return nil
// }
// guard let uniqueID = row.string(forColumn: DatabaseKey.uniqueID) else {
// return nil
// }
//
// let articleID = row.string(forColumn: DatabaseKey.articleID)!
// let title = row.string(forColumn: DatabaseKey.title)
// let contentHTML = row.string(forColumn: DatabaseKey.contentHTML)
// let contentText = row.string(forColumn: DatabaseKey.contentText)
// let url = row.string(forColumn: DatabaseKey.url)
// let externalURL = row.string(forColumn: DatabaseKey.externalURL)
// let summary = row.string(forColumn: DatabaseKey.summary)
// let imageURL = row.string(forColumn: DatabaseKey.imageURL)
// let bannerImageURL = row.string(forColumn: DatabaseKey.bannerImageURL)
// let datePublished = row.date(forColumn: DatabaseKey.datePublished)
// let dateModified = row.date(forColumn: DatabaseKey.dateModified)
// let accountInfo: AccountInfo? = nil // TODO
//
// 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, status: status)
// }
init?(dictionary: [String: Any], accountID: String, status: ArticleStatus, authors: Set<Author>?, attachments: Set<Attachment>?, tags: Set<String>?) {
guard let articleID = dictionary[DatabaseKey.articleID], let feedID = dictionary[DatabaseKey.feedID], let uniqueID = dictionary[DatabaseKey.uniqueID] else {
guard let articleID = dictionary[DatabaseKey.articleID] as? String, let feedID = dictionary[DatabaseKey.feedID] as? String, let uniqueID = dictionary[DatabaseKey.uniqueID] as? String else {
return nil
}
@ -53,7 +53,7 @@ extension Article {
let imageURL = dictionary[DatabaseKey.imageURL] as? String
let bannerImageURL = dictionary[DatabaseKey.bannerImageURL] as? String
let datePublished = dictionary[DatabaseKey.datePublished] as? Date
let dateModified = dictionary[DatabaseKey.dateModified]? as? Date
let dateModified = dictionary[DatabaseKey.dateModified] as? Date
let accountInfo: AccountInfo? = nil // TODO
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, status: status)

View File

@ -95,6 +95,21 @@ final class StatusesTable: DatabaseTable {
return articleStatus
}
func statusesDictionary(_ articleIDs: Set<String>) -> [String: ArticleStatus] {
assert(!Thread.isMainThread)
var d = [String: ArticleStatus]()
for articleID in articleIDs {
if let articleStatus = cache[articleID] {
d[articleID] = articleStatus
}
}
return d
}
}
// MARK: - Private
@ -109,21 +124,6 @@ private extension StatusesTable {
return Set(articleIDs.filter { cache[$0] == nil })
}
func statusesDictionary(_ articleIDs: Set<String>) -> [String: ArticleStatus] {
assert(!Thread.isMainThread)
var d = [String: ArticleStatus]()
for articleID in articleIDs {
if let articleStatus = cache[articleID] {
d[articleID] = articleStatus
}
}
return d
}
// MARK: Creating
func saveStatuses(_ statuses: Set<ArticleStatus>, _ database: FMDatabase) {