diff --git a/Frameworks/Database/ArticlesTable.swift b/Frameworks/Database/ArticlesTable.swift index 6218e1576..7c8bf0ef1 100644 --- a/Frameworks/Database/ArticlesTable.swift +++ b/Frameworks/Database/ArticlesTable.swift @@ -158,54 +158,74 @@ private extension ArticlesTable { func articlesWithResultSet(_ resultSet: FMResultSet, _ database: FMDatabase) -> Set
{ - // 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
() } - // 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 { + // Don’t 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
{ - - var stubArticles = Set
() - - // 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
{ // Don’t fetch articles that shouldn’t appear in the UI. The rules: diff --git a/Frameworks/Database/Extensions/Article+Database.swift b/Frameworks/Database/Extensions/Article+Database.swift index 940c9e905..fde131001 100644 --- a/Frameworks/Database/Extensions/Article+Database.swift +++ b/Frameworks/Database/Extensions/Article+Database.swift @@ -13,34 +13,34 @@ import RSParser extension Article { - init?(row: FMResultSet, accountID: String, authors: Set? = nil, attachments: Set? = nil, tags: Set? = 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? = nil, attachments: Set? = nil, tags: Set? = 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?, attachments: Set?, tags: Set?) { - 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) diff --git a/Frameworks/Database/StatusesTable.swift b/Frameworks/Database/StatusesTable.swift index 76e4aeb4a..d48148a9b 100644 --- a/Frameworks/Database/StatusesTable.swift +++ b/Frameworks/Database/StatusesTable.swift @@ -95,6 +95,21 @@ final class StatusesTable: DatabaseTable { return articleStatus } + + func statusesDictionary(_ articleIDs: Set) -> [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: 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, _ database: FMDatabase) {