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
|
|
|
|
|
import Data
|
|
|
|
|
|
2017-08-06 21:37:47 +02:00
|
|
|
|
// Article->ArticleStatus is a to-one relationship.
|
|
|
|
|
//
|
|
|
|
|
// CREATE TABLE if not EXISTS statuses (articleID TEXT NOT NULL PRIMARY KEY, read BOOL NOT NULL DEFAULT 0, starred BOOL NOT NULL DEFAULT 0, userDeleted BOOL NOT NULL DEFAULT 0, dateArrived DATE NOT NULL DEFAULT 0, accountInfo BLOB);
|
|
|
|
|
|
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-08-21 02:46:15 +02:00
|
|
|
|
private let cache = DatabaseObjectCache()
|
2017-07-29 21:13:38 +02:00
|
|
|
|
|
2017-08-21 00:56:58 +02:00
|
|
|
|
init(name: String) {
|
2017-07-29 21:13:38 +02:00
|
|
|
|
|
2017-07-29 21:08:10 +02:00
|
|
|
|
self.name = name
|
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-08-05 20:12:45 +02:00
|
|
|
|
|
|
|
|
|
// Main thread.
|
|
|
|
|
|
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) {
|
|
|
|
|
|
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 shouldn’t 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-07-03 19:40:48 +02:00
|
|
|
|
}
|
2017-08-05 20:12:45 +02:00
|
|
|
|
createAndSaveStatusesForArticles(articlesNeedingStatusesCreated, database)
|
|
|
|
|
|
|
|
|
|
assertNoMissingStatuses(articles)
|
2017-07-03 19:40:48 +02:00
|
|
|
|
}
|
2017-08-01 03:39:42 +02:00
|
|
|
|
|
|
|
|
|
|
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-01 03:39:42 +02:00
|
|
|
|
|
2017-08-05 20:12:45 +02:00
|
|
|
|
if let cachedStatus = cache[oneArticle.databaseID] {
|
|
|
|
|
oneArticle.status = cachedStatus
|
2017-07-03 19:40:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
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-05 20:12:45 +02:00
|
|
|
|
|
|
|
|
|
func fetchAndCacheStatusesForArticles(_ articles: Set<Article>, _ database: FMDatabase) {
|
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
fetchAndCacheStatusesForArticleIDs(articles.articleIDs(), database)
|
2017-08-05 20:12:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func fetchAndCacheStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) {
|
|
|
|
|
|
2017-08-06 21:37:47 +02:00
|
|
|
|
if let statuses = fetchStatusesForArticleIDs(articleIDs, database) {
|
|
|
|
|
cache.addObjectsNotCached(Array(statuses))
|
|
|
|
|
}
|
2017-08-05 20:12:45 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-08-06 21:37:47 +02:00
|
|
|
|
func fetchStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) -> Set<ArticleStatus>? {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
|
2017-08-06 21:37:47 +02:00
|
|
|
|
guard let resultSet = selectRowsWhere(key: DatabaseKey.articleID, inValues: Array(articleIDs), in: database) else {
|
|
|
|
|
return nil
|
2017-07-03 19:40:48 +02:00
|
|
|
|
}
|
2017-08-06 21:37:47 +02:00
|
|
|
|
return articleStatusesWithResultSet(resultSet)
|
2017-07-03 19:40:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
2017-07-03 20:20:14 +02:00
|
|
|
|
func articleStatusesWithResultSet(_ resultSet: FMResultSet) -> Set<ArticleStatus> {
|
2017-08-06 21:37:47 +02:00
|
|
|
|
|
|
|
|
|
return resultSet.mapToSet(articleStatusWithRow)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func articleStatusWithRow(_ row: FMResultSet) -> ArticleStatus? {
|
|
|
|
|
|
|
|
|
|
guard let articleID = row.string(forColumn: DatabaseKey.articleID) else {
|
|
|
|
|
return nil
|
2017-07-03 19:40:48 +02:00
|
|
|
|
}
|
2017-08-06 21:37:47 +02:00
|
|
|
|
if let cachedStatus = cache[articleID] {
|
|
|
|
|
return cachedStatus
|
|
|
|
|
}
|
|
|
|
|
let status = ArticleStatus(articleID: articleID, row: row)
|
|
|
|
|
cache[articleID] = status
|
|
|
|
|
return status
|
2017-07-03 19:40:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
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-05 20:12:45 +02:00
|
|
|
|
func saveStatuses(_ statuses: Set<ArticleStatus>, _ database: FMDatabase) {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
|
2017-08-01 03:39:42 +02:00
|
|
|
|
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-01 03:39:42 +02:00
|
|
|
|
}
|
2017-07-03 19:40:48 +02:00
|
|
|
|
|
2017-08-05 20:12:45 +02:00
|
|
|
|
func createAndSaveStatusesForArticleIDs(_ articleIDs: Set<String>, _ database: FMDatabase) {
|
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-05 20:12:45 +02:00
|
|
|
|
saveStatuses(Set(statuses), database)
|
2017-07-03 19:40:48 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// MARK: Utilities
|
2017-08-05 20:12:45 +02:00
|
|
|
|
|
|
|
|
|
func articleIDsMissingCachedStatuses(_ articleIDs: Set<String>) -> Set<String> {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
|
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> {
|
|
|
|
|
|
2017-08-07 06:16:13 +02:00
|
|
|
|
return articles.withNilProperty(\Article.status)
|
2017-07-03 19:40:48 +02:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2017-08-05 20:12:45 +02:00
|
|
|
|
//extension ParsedItem {
|
|
|
|
|
//
|
|
|
|
|
// var articleID: String {
|
|
|
|
|
// get {
|
|
|
|
|
// return "\(feedURL) \(uniqueID)" //Must be same as Article.articleID
|
|
|
|
|
// }
|
|
|
|
|
// }
|
|
|
|
|
//}
|
2017-07-03 19:40:48 +02:00
|
|
|
|
|