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