// // 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 import RSParser import Data public typealias ArticleResultBlock = (Set
) -> Void public typealias UnreadCountTable = [String: Int] // feedID: unreadCount public typealias UnreadCountCompletionBlock = (UnreadCountTable) -> Void //feedID: unreadCount public final class Database { private let queue: RSDatabaseQueue private let databaseFile: String private let articlesTable: ArticlesTable private var articleArrivalCutoffDate = NSDate.rs_dateWithNumberOfDays(inThePast: 3 * 31)! private let minimumNumberOfArticles = 10 private weak var delegate: AccountDelegate? private weak var account: Account? public init(databaseFile: String, delegate: AccountDelegate, account: Account) { self.delegate = delegate self.account = account self.databaseFile = databaseFile self.queue = RSDatabaseQueue(filepath: databaseFile, excludeFromBackup: false) self.articlesTable = ArticlesTable(name: DatabaseTableName.articles, account: account, queue: queue) let createStatementsPath = Bundle(for: type(of: self)).path(forResource: "CreateStatements", ofType: "sql")! let createStatements = try! NSString(contentsOfFile: createStatementsPath, encoding: String.Encoding.utf8.rawValue) queue.createTables(usingStatements: createStatements as String) queue.vacuumIfNeeded() } // MARK: - Fetching Articles public func fetchArticles(for feed: Feed) -> Set
{ return articlesTable.fetchArticles(feed) } public func fetchArticlesAsync(for feed: Feed, _ resultBlock: @escaping ArticleResultBlock) { articlesTable.fetchArticlesAsync(feed, resultBlock) } public func fetchUnreadArticles(for folder: Folder) -> Set
{ return articlesTable.fetchUnreadArticles(for: folder.flattenedFeeds()) } // MARK: - Unread Counts public func fetchUnreadCounts(for feeds: Set, _ completion: @escaping UnreadCountCompletionBlock) { articlesTable.fetchUnreadCounts(feeds, completion) } // MARK: - Updating Articles public func update(feed: Feed, parsedFeed: ParsedFeed, completion: @escaping RSVoidCompletionBlock) { return articlesTable.update(feed, parsedFeed, completion) // if parsedFeed.items.isEmpty { // completionHandler() // return // } // // let parsedArticlesDictionary = self.articlesDictionary(parsedFeed.items as NSSet) as! [String: ParsedItem] // // fetchArticlesForFeedAsync(feed) { (articles) -> Void in // // let articlesDictionary = self.articlesDictionary(articles as NSSet) as! [String: Article] // self.updateArticles(articlesDictionary, parsedArticles: parsedArticlesDictionary, feed: feed, completionHandler: completionHandler) // } } // MARK: - Status public func mark(_ articles: Set
, statusKey: String, flag: Bool) { articlesTable.mark(articles, statusKey, flag) } } // MARK: - Private private extension Database { // MARK: Saving Articles // func saveUpdatedAndNewArticles(_ articleChanges: Set, newArticles: Set
) { // // 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 { // // var articleChanges = Set() // // 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, feedID: String) -> Set
{ // // return Set(parsedArticles.map { Article(account: account, feedID: feedID, parsedArticle: $0) }) // } // // func articlesWithParsedArticles(_ parsedArticles: Set, feedID: String) -> Set
{ // // var localArticles = Set
() // // 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
{ // // 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 { // // var result = Set() // // 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
) { // // let feedIDs = feedIDsFromArticles(articles) // if feedIDs.isEmpty { // return // } // } }