NetNewsWire/Frameworks/Database/StatusesTable.swift

235 lines
6.1 KiB
Swift
Raw Normal View History

//
// StatusesTable.swift
// Evergreen
//
// Created by Brent Simmons on 5/8/16.
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
//
import Foundation
import RSCore
import RSDatabase
import Data
final class StatusesTable: DatabaseTable {
2017-07-30 20:22:21 +02:00
let name: String
2017-07-29 21:13:38 +02:00
let queue: RSDatabaseQueue
private let cache = ObjectCache<ArticleStatus>(keyPathForID: \ArticleStatus.articleID)
2017-07-29 21:13:38 +02:00
init(name: String, queue: RSDatabaseQueue) {
self.name = name
2017-07-29 21:13:38 +02:00
self.queue = queue
}
2017-07-29 21:13:38 +02:00
2017-07-04 00:04:31 +02:00
func markArticles(_ articles: Set<Article>, statusKey: String, flag: Bool) {
2017-08-05 20:12:45 +02:00
// Main thread.
assertNoMissingStatuses(articles)
let statuses = Set(articles.flatMap { $0.status })
markArticleStatuses(statuses, statusKey: statusKey, flag: flag)
2017-08-04 06:10:01 +02:00
}
func attachStatuses(_ articles: Set<Article>, _ database: FMDatabase) {
2017-08-05 20:12:45 +02:00
// Look in cache first.
2017-08-04 06:10:01 +02:00
attachCachedStatuses(articles)
let articlesNeedingStatuses = articlesMissingStatuses(articles)
if articlesNeedingStatuses.isEmpty {
return
}
2017-08-05 20:12:45 +02:00
// Fetch from database.
fetchAndCacheStatusesForArticles(articlesNeedingStatuses, database)
2017-08-04 06:10:01 +02:00
attachCachedStatuses(articlesNeedingStatuses)
2017-08-05 20:12:45 +02:00
// Create new statuses, and cache and save them in the database.
2017-08-04 06:10:01 +02:00
// It shouldnt happen that an Article in the database has no corresponding ArticleStatus,
// but the case should be handled anyway.
2017-08-05 20:12:45 +02:00
let articlesNeedingStatusesCreated = articlesMissingStatuses(articlesNeedingStatuses)
if articlesNeedingStatusesCreated.isEmpty {
return
}
2017-08-05 20:12:45 +02:00
createAndSaveStatusesForArticles(articlesNeedingStatusesCreated, database)
assertNoMissingStatuses(articles)
}
2017-08-05 20:12:45 +02:00
// func ensureStatusesForParsedArticles(_ parsedArticles: [ParsedItem], _ callback: @escaping RSVoidCompletionBlock) {
//
// // 1. Check cache for statuses
// // 2. Fetch statuses not found in cache
// // 3. Create, save, and cache statuses not found in database
//
// var articleIDs = Set(parsedArticles.map { $0.articleID })
// articleIDs = articleIDsMissingStatuses(articleIDs)
// if articleIDs.isEmpty {
// callback()
// return
// }
//
// queue.fetch { (database: FMDatabase!) -> Void in
//
// let statuses = self.fetchStatusesForArticleIDs(articleIDs, database: database)
//
// DispatchQueue.main.async {
//
// self.cache.addObjectsNotCached(Array(statuses))
//
// let newArticleIDs = self.articleIDsMissingStatuses(articleIDs)
// if !newArticleIDs.isEmpty {
// self.createAndSaveStatusesForArticleIDs(newArticleIDs)
// }
//
// callback()
// }
// }
// }
}
2017-07-30 20:36:27 +02:00
2017-08-05 20:12:45 +02:00
private extension StatusesTable {
func attachCachedStatuses(_ articles: Set<Article>) {
2017-07-30 20:36:27 +02:00
2017-08-05 20:12:45 +02:00
articles.forEach { (oneArticle) in
2017-08-05 20:12:45 +02:00
if let cachedStatus = cache[oneArticle.databaseID] {
oneArticle.status = cachedStatus
}
}
}
func assertNoMissingStatuses(_ articles: Set<Article>) {
for oneArticle in articles {
if oneArticle.status == nil {
assertionFailure("All articles must have a status at this point.")
return
}
}
}
// MARK: Fetching
2017-08-05 20:12:45 +02:00
func fetchAndCacheStatusesForArticles(_ articles: Set<Article>, _ database: FMDatabase) {
fetchAndCacheStatusesForArticleIDs(articleIDsFromArticles(articles), database)
}
func fetchAndCacheStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) {
let statuses = fetchStatusesForArticleIDs(articleIDs, database)
cache.addObjectsNotCached(Array(statuses))
}
2017-08-04 06:10:01 +02:00
func fetchStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) -> Set<ArticleStatus> {
if !articleIDs.isEmpty, let resultSet = selectRowsWhere(key: DatabaseKey.articleID, inValues: Array(articleIDs), in: database) {
return articleStatusesWithResultSet(resultSet)
}
return Set<ArticleStatus>()
}
func articleStatusesWithResultSet(_ resultSet: FMResultSet) -> Set<ArticleStatus> {
var statuses = Set<ArticleStatus>()
while(resultSet.next()) {
if let oneArticleStatus = ArticleStatus(row: resultSet) {
statuses.insert(oneArticleStatus)
}
}
return statuses
}
// MARK: Updating
func markArticleStatuses(_ statuses: Set<ArticleStatus>, statusKey: String, flag: Bool) {
// Ignore the statuses where status.[statusKey] == flag. Update the remainder and save in database.
var articleIDsToUpdate = Set<String>()
statuses.forEach { (oneStatus) in
if oneStatus.boolStatus(forKey: statusKey) == flag {
return
}
oneStatus.setBoolStatus(flag, forKey: statusKey)
articleIDsToUpdate.insert(oneStatus.articleID)
}
if !articleIDsToUpdate.isEmpty {
updateArticleStatusesInDatabase(articleIDsToUpdate, statusKey: statusKey, flag: flag)
}
}
private func updateArticleStatusesInDatabase(_ articleIDs: Set<String>, statusKey: String, flag: Bool) {
updateRowsWithValue(NSNumber(value: flag), valueKey: statusKey, whereKey: DatabaseKey.articleID, matches: Array(articleIDs))
}
// MARK: Creating
2017-08-05 20:12:45 +02:00
func saveStatuses(_ statuses: Set<ArticleStatus>, _ database: FMDatabase) {
let statusArray = statuses.map { $0.databaseDictionary() }
2017-08-05 20:12:45 +02:00
insertRows(statusArray, insertType: .orIgnore, in: database)
}
func createAndSaveStatusesForArticles(_ articles: Set<Article>, _ database: FMDatabase) {
let articleIDs = Set(articles.map { $0.databaseID })
createAndSaveStatusesForArticleIDs(articleIDs, database)
}
2017-08-05 20:12:45 +02:00
func createAndSaveStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) {
let now = Date()
let statuses = articleIDs.map { ArticleStatus(articleID: $0, dateArrived: now) }
cache.addObjectsNotCached(statuses)
2017-08-05 20:12:45 +02:00
saveStatuses(Set(statuses), database)
}
// MARK: Utilities
2017-08-05 20:12:45 +02:00
func articleIDsFromArticles(_ articles: Set<Article>) -> Set<String> {
return Set(articles.map { $0.databaseID })
}
func articleIDsMissingCachedStatuses(_ articleIDs: Set<String>) -> Set<String> {
2017-08-05 20:12:45 +02:00
return Set(articleIDs.filter { !cache.objectWithIDIsCached($0) })
2017-08-04 06:10:01 +02:00
}
func articlesMissingStatuses(_ articles: Set<Article>) -> Set<Article> {
let missing = articles.flatMap { (article) -> Article? in
if article.status == nil {
return article
}
return nil
}
return Set(missing)
}
}
2017-08-05 20:12:45 +02:00
//extension ParsedItem {
//
// var articleID: String {
// get {
// return "\(feedURL) \(uniqueID)" //Must be same as Article.articleID
// }
// }
//}