2017-07-03 19:40:48 +02:00
|
|
|
//
|
|
|
|
// Database.swift
|
|
|
|
// Evergreen
|
|
|
|
//
|
|
|
|
// Created by Brent Simmons on 7/20/15.
|
|
|
|
// Copyright © 2015 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-08-23 22:23:12 +02:00
|
|
|
public typealias ArticleResultBlock = (Set<Article>) -> Void
|
|
|
|
public typealias UnreadCountTable = [String: Int] // feedID: unreadCount
|
|
|
|
public typealias UnreadCountCompletionBlock = (UnreadCountTable) -> Void //feedID: unreadCount
|
2017-09-10 03:46:58 +02:00
|
|
|
public typealias UpdateArticlesWithFeedCompletionBlock = (Set<Article>?, Set<Article>?) -> Void //newArticles, updateArticles
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-08-23 22:23:12 +02:00
|
|
|
public final class Database {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-09-10 03:46:58 +02:00
|
|
|
private let accountID: String
|
2017-08-21 00:56:58 +02:00
|
|
|
private let queue: RSDatabaseQueue
|
2017-07-03 19:40:48 +02:00
|
|
|
private let databaseFile: String
|
2017-08-21 22:31:14 +02:00
|
|
|
private let articlesTable: ArticlesTable
|
2017-08-21 00:56:58 +02:00
|
|
|
private var articleArrivalCutoffDate = NSDate.rs_dateWithNumberOfDays(inThePast: 3 * 31)!
|
|
|
|
private let minimumNumberOfArticles = 10
|
2017-08-27 00:37:15 +02:00
|
|
|
|
2017-09-10 03:46:58 +02:00
|
|
|
public init(databaseFile: String, accountID: String) {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-09-10 03:46:58 +02:00
|
|
|
self.accountID = accountID
|
2017-07-03 19:40:48 +02:00
|
|
|
self.databaseFile = databaseFile
|
|
|
|
self.queue = RSDatabaseQueue(filepath: databaseFile, excludeFromBackup: false)
|
2017-08-04 06:10:01 +02:00
|
|
|
|
2017-09-10 03:46:58 +02:00
|
|
|
self.articlesTable = ArticlesTable(name: DatabaseTableName.articles, accountID: accountID, queue: queue)
|
2017-08-21 00:56:58 +02:00
|
|
|
|
2017-07-03 20:20:14 +02:00
|
|
|
let createStatementsPath = Bundle(for: type(of: self)).path(forResource: "CreateStatements", ofType: "sql")!
|
2017-07-03 19:40:48 +02:00
|
|
|
let createStatements = try! NSString(contentsOfFile: createStatementsPath, encoding: String.Encoding.utf8.rawValue)
|
|
|
|
queue.createTables(usingStatements: createStatements as String)
|
|
|
|
queue.vacuumIfNeeded()
|
|
|
|
}
|
|
|
|
|
2017-08-21 06:23:17 +02:00
|
|
|
// MARK: - Fetching Articles
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-08-23 22:23:12 +02:00
|
|
|
public func fetchArticles(for feed: Feed) -> Set<Article> {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-08-23 22:23:12 +02:00
|
|
|
return articlesTable.fetchArticles(feed)
|
2017-07-03 19:40:48 +02:00
|
|
|
}
|
|
|
|
|
2017-08-23 22:23:12 +02:00
|
|
|
public func fetchArticlesAsync(for feed: Feed, _ resultBlock: @escaping ArticleResultBlock) {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-09-05 02:10:02 +02:00
|
|
|
articlesTable.fetchArticlesAsync(feed, withLimits: true, resultBlock)
|
2017-07-03 19:40:48 +02:00
|
|
|
}
|
|
|
|
|
2017-08-23 22:23:12 +02:00
|
|
|
public func fetchUnreadArticles(for folder: Folder) -> Set<Article> {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-08-27 22:03:15 +02:00
|
|
|
return articlesTable.fetchUnreadArticles(for: folder.flattenedFeeds())
|
2017-07-03 19:40:48 +02:00
|
|
|
}
|
2017-08-21 06:23:17 +02:00
|
|
|
|
|
|
|
// MARK: - Unread Counts
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-09-01 22:31:27 +02:00
|
|
|
public func fetchUnreadCounts(for feeds: Set<Feed>, _ completion: @escaping UnreadCountCompletionBlock) {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-09-01 22:31:27 +02:00
|
|
|
articlesTable.fetchUnreadCounts(feeds, completion)
|
2017-07-03 19:40:48 +02:00
|
|
|
}
|
|
|
|
|
2017-08-21 06:23:17 +02:00
|
|
|
// MARK: - Updating Articles
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-09-10 03:46:58 +02:00
|
|
|
public func update(feed: Feed, parsedFeed: ParsedFeed, completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-08-23 22:23:12 +02:00
|
|
|
return articlesTable.update(feed, parsedFeed, completion)
|
2017-07-03 19:40:48 +02:00
|
|
|
}
|
|
|
|
|
2017-08-21 06:23:17 +02:00
|
|
|
// MARK: - Status
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-08-23 22:23:12 +02:00
|
|
|
public func mark(_ articles: Set<Article>, statusKey: String, flag: Bool) {
|
2017-09-01 22:31:27 +02:00
|
|
|
|
2017-08-23 22:23:12 +02:00
|
|
|
articlesTable.mark(articles, statusKey, flag)
|
2017-07-03 19:40:48 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-08-21 06:23:17 +02:00
|
|
|
// MARK: - Private
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-07-04 00:04:31 +02:00
|
|
|
private extension Database {
|
2017-07-03 19:40:48 +02:00
|
|
|
|
|
|
|
|
2017-08-21 06:23:17 +02:00
|
|
|
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-08-21 06:23:17 +02:00
|
|
|
// MARK: Saving Articles
|
2017-07-03 19:40:48 +02:00
|
|
|
|
2017-08-21 06:23:17 +02:00
|
|
|
// func saveUpdatedAndNewArticles(_ articleChanges: Set<NSDictionary>, newArticles: Set<Article>) {
|
|
|
|
//
|
|
|
|
// if articleChanges.isEmpty && newArticles.isEmpty {
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// statusesTable.assertNoMissingStatuses(newArticles)
|
|
|
|
// articleCache.cacheArticles(newArticles)
|
|
|
|
//
|
|
|
|
// let newArticleDictionaries = newArticles.map { (oneArticle) in
|
|
|
|
// return oneArticle.databaseDictionary()
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// queue.update { (database: FMDatabase!) -> Void in
|
|
|
|
//
|
|
|
|
// if !articleChanges.isEmpty {
|
|
|
|
//
|
|
|
|
// for oneDictionary in articleChanges {
|
|
|
|
//
|
|
|
|
// let oneArticleDictionary = oneDictionary.mutableCopy() as! NSMutableDictionary
|
|
|
|
// let articleID = oneArticleDictionary[DatabaseKey.articleID]!
|
|
|
|
// oneArticleDictionary.removeObject(forKey: DatabaseKey.articleID)
|
|
|
|
//
|
|
|
|
// let _ = database.rs_updateRows(with: oneArticleDictionary as [NSObject: AnyObject], whereKey: DatabaseKey.articleID, equalsValue: articleID, tableName: DatabaseTableName.articles)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// }
|
|
|
|
// if !newArticleDictionaries.isEmpty {
|
|
|
|
//
|
|
|
|
// for oneNewArticleDictionary in newArticleDictionaries {
|
|
|
|
// let _ = database.rs_insertRow(with: oneNewArticleDictionary as [NSObject: AnyObject], insertType: RSDatabaseInsertOrReplace, tableName: DatabaseTableName.articles)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // MARK: Updating Articles
|
|
|
|
//
|
|
|
|
// func updateArticles(_ articles: [String: Article], parsedArticles: [String: ParsedItem], feed: Feed, completionHandler: @escaping RSVoidCompletionBlock) {
|
|
|
|
//
|
|
|
|
// statusesTable.ensureStatusesForParsedArticles(Set(parsedArticles.values)) {
|
|
|
|
//
|
|
|
|
// let articleChanges = self.updateExistingArticles(articles, parsedArticles)
|
|
|
|
// let newArticles = self.createNewArticles(articles, parsedArticles: parsedArticles, feedID: feed.feedID)
|
|
|
|
//
|
|
|
|
// self.saveUpdatedAndNewArticles(articleChanges, newArticles: newArticles)
|
|
|
|
//
|
|
|
|
// completionHandler()
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func articlesDictionary(_ articles: NSSet) -> [String: AnyObject] {
|
|
|
|
//
|
|
|
|
// var d = [String: AnyObject]()
|
|
|
|
// for oneArticle in articles {
|
|
|
|
// let oneArticleID = (oneArticle as AnyObject).value(forKey: DatabaseKey.articleID) as! String
|
|
|
|
// d[oneArticleID] = oneArticle as AnyObject
|
|
|
|
// }
|
|
|
|
// return d
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func updateExistingArticles(_ articles: [String: Article], _ parsedArticles: [String: ParsedItem]) -> Set<NSDictionary> {
|
|
|
|
//
|
|
|
|
// var articleChanges = Set<NSDictionary>()
|
|
|
|
//
|
|
|
|
// for oneArticle in articles.values {
|
|
|
|
// if let oneParsedArticle = parsedArticles[oneArticle.articleID] {
|
|
|
|
// if let oneArticleChanges = oneArticle.updateWithParsedArticle(oneParsedArticle) {
|
|
|
|
// articleChanges.insert(oneArticleChanges)
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return articleChanges
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // MARK: Creating Articles
|
|
|
|
//
|
|
|
|
// func createNewArticlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> {
|
|
|
|
//
|
|
|
|
// return Set(parsedArticles.map { Article(account: account, feedID: feedID, parsedArticle: $0) })
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func articlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> {
|
|
|
|
//
|
|
|
|
// var localArticles = Set<Article>()
|
|
|
|
//
|
|
|
|
// for oneParsedArticle in parsedArticles {
|
|
|
|
// let oneLocalArticle = Article(account: self.account, feedID: feedID, parsedArticle: oneParsedArticle)
|
|
|
|
// localArticles.insert(oneLocalArticle)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return localArticles
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func createNewArticles(_ existingArticles: [String: Article], parsedArticles: [String: ParsedItem], feedID: String) -> Set<Article> {
|
|
|
|
//
|
|
|
|
// let newParsedArticles = parsedArticlesMinusExistingArticles(parsedArticles, existingArticles: existingArticles)
|
|
|
|
// let newArticles = createNewArticlesWithParsedArticles(newParsedArticles, feedID: feedID)
|
|
|
|
//
|
|
|
|
// statusesTable.attachCachedUniqueStatuses(newArticles)
|
|
|
|
//
|
|
|
|
// return newArticles
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func parsedArticlesMinusExistingArticles(_ parsedArticles: [String: ParsedItem], existingArticles: [String: Article]) -> Set<ParsedItem> {
|
|
|
|
//
|
|
|
|
// var result = Set<ParsedItem>()
|
|
|
|
//
|
|
|
|
// for oneParsedArticle in parsedArticles.values {
|
|
|
|
//
|
|
|
|
// if let _ = existingArticles[oneParsedArticle.databaseID] {
|
|
|
|
// continue
|
|
|
|
// }
|
|
|
|
// result.insert(oneParsedArticle)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// return result
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// // MARK: Filtering out old articles
|
|
|
|
//
|
|
|
|
// func articleIsOlderThanCutoffDate(_ article: Article) -> Bool {
|
|
|
|
//
|
|
|
|
// if let dateArrived = article.status?.dateArrived {
|
|
|
|
// return dateArrived < articleArrivalCutoffDate
|
|
|
|
// }
|
|
|
|
// return false
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
// func articleShouldAppearToUser(_ article: Article, _ numberOfArticlesInFeed: Int) -> Bool {
|
|
|
|
//
|
|
|
|
// if numberOfArticlesInFeed <= minimumNumberOfArticles {
|
|
|
|
// return true
|
|
|
|
// }
|
|
|
|
// return articleShouldBeSavedForever(article) || !articleIsOlderThanCutoffDate(article)
|
|
|
|
// }
|
|
|
|
//
|
|
|
|
//
|
|
|
|
// func deletePossibleOldArticles(_ articles: Set<Article>) {
|
|
|
|
//
|
|
|
|
// let feedIDs = feedIDsFromArticles(articles)
|
|
|
|
// if feedIDs.isEmpty {
|
|
|
|
// return
|
|
|
|
// }
|
|
|
|
// }
|
2017-07-03 19:40:48 +02:00
|
|
|
}
|