Work on getting Database.framework to build.
This commit is contained in:
parent
0379e5f525
commit
d47c60e6de
@ -11,24 +11,24 @@ import Foundation
|
||||
public final class Article: Hashable {
|
||||
|
||||
weak var account: Account?
|
||||
let feedID: String
|
||||
let articleID: String //Calculated: unique per account
|
||||
public let feedID: String
|
||||
public let articleID: String //Calculated: unique per account
|
||||
|
||||
var uniqueID: String //guid: unique per feed
|
||||
var title: String?
|
||||
var contentHTML: String?
|
||||
var contentText: String?
|
||||
var url: String?
|
||||
var externalURL: String?
|
||||
var summary: String?
|
||||
var imageURL: String?
|
||||
var bannerImageURL: String?
|
||||
var datePublished: Date?
|
||||
var dateModified: Date?
|
||||
var authors: [Author]?
|
||||
var tags: [String]?
|
||||
var attachments: [Attachment]?
|
||||
var status: ArticleStatus?
|
||||
public var uniqueID: String //guid: unique per feed
|
||||
public var title: String?
|
||||
public var contentHTML: String?
|
||||
public var contentText: String?
|
||||
public var url: String?
|
||||
public var externalURL: String?
|
||||
public var summary: String?
|
||||
public var imageURL: String?
|
||||
public var bannerImageURL: String?
|
||||
public var datePublished: Date?
|
||||
public var dateModified: Date?
|
||||
public var authors: [Author]?
|
||||
public var tags: [String]?
|
||||
public var attachments: [Attachment]?
|
||||
public var status: ArticleStatus?
|
||||
public let hashValue: Int
|
||||
|
||||
public var accountInfo: [String: Any]? //If account needs to store more data
|
||||
|
@ -15,13 +15,14 @@ public enum ArticleStatusKey: String {
|
||||
case userDeleted = "userDeleted"
|
||||
}
|
||||
|
||||
public final class ArticleStatus {
|
||||
public final class ArticleStatus: Hashable {
|
||||
|
||||
public var read = false
|
||||
public var starred = false
|
||||
public var userDeleted = false
|
||||
public var dateArrived: Date
|
||||
var accountInfo: AccountInfo?
|
||||
public let hashValue: Int
|
||||
|
||||
init(read: Bool, starred: Bool, userDeleted: Bool, dateArrived: Date, accountInfo: AccountInfo?) {
|
||||
|
||||
@ -30,6 +31,7 @@ public final class ArticleStatus {
|
||||
self.userDeleted = userDeleted
|
||||
self.dateArrived = dateArrived
|
||||
self.accountInfo = accountInfo
|
||||
self.hashValue = dateArrived.hashValue
|
||||
}
|
||||
|
||||
func boolStatusForKey(_ key: String) -> Bool {
|
||||
@ -69,4 +71,9 @@ public final class ArticleStatus {
|
||||
accountInfo![key] = status
|
||||
}
|
||||
}
|
||||
|
||||
public class func ==(lhs: ArticleStatus, rhs: ArticleStatus) -> Bool {
|
||||
|
||||
return lhs.dateArrived == rhs.dateArrived && lhs.read == rhs.read && lhs.starred == rhs.starred
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public protocol AccountDelegate {
|
||||
public protocol AccountDelegate: class {
|
||||
|
||||
func canAddItem(_ item: AnyObject, toContainer: Container) -> Bool
|
||||
|
||||
|
@ -1,94 +0,0 @@
|
||||
//
|
||||
// LocalArticleCache.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 5/9/16.
|
||||
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
|
||||
final class LocalArticleCache {
|
||||
|
||||
private var cachedArticles: NSMapTable<NSString, LocalArticle> = NSMapTable.weakToWeakObjects()
|
||||
// private var cachedArticles = [String: LocalArticle]()
|
||||
// fileprivate var articlesByFeedID = [String: Set<LocalArticle>]()
|
||||
private let statusesManager: LocalStatusesManager
|
||||
|
||||
init(statusesManager: LocalStatusesManager) {
|
||||
|
||||
self.statusesManager = statusesManager
|
||||
}
|
||||
|
||||
func uniquedArticles(_ fetchedArticles: Set<LocalArticle>) -> Set<LocalArticle> {
|
||||
|
||||
var articles = Set<LocalArticle>()
|
||||
|
||||
for oneArticle in fetchedArticles {
|
||||
|
||||
assert(oneArticle.status != nil)
|
||||
|
||||
if let existingArticle = cachedArticle(oneArticle.articleID) {
|
||||
articles.insert(existingArticle)
|
||||
}
|
||||
else {
|
||||
cacheArticle(oneArticle)
|
||||
articles.insert(oneArticle)
|
||||
}
|
||||
}
|
||||
|
||||
statusesManager.attachCachedUniqueStatuses(articles)
|
||||
|
||||
return articles
|
||||
}
|
||||
|
||||
func cachedArticle(_ articleID: String) -> LocalArticle? {
|
||||
|
||||
return cachedArticles.object(forKey: articleID as NSString)
|
||||
// return cachedArticles[articleID]
|
||||
}
|
||||
|
||||
func cacheArticle(_ article: LocalArticle) {
|
||||
|
||||
cachedArticles.setObject(article, forKey: article.articleID as NSString)
|
||||
// cachedArticles[article.articleID] = article
|
||||
// addToCachedArticlesForFeedID(Set([article]))
|
||||
}
|
||||
|
||||
func cacheArticles(_ articles: Set<LocalArticle>) {
|
||||
|
||||
articles.forEach { cacheArticle($0) }
|
||||
// addToCachedArticlesForFeedID(articles)
|
||||
}
|
||||
|
||||
// func cachedArticlesForFeedID(_ feedID: String) -> Set<LocalArticle>? {
|
||||
//
|
||||
// return articlesByFeedID[feedID]
|
||||
// }
|
||||
}
|
||||
|
||||
//private extension LocalArticleCache {
|
||||
//
|
||||
// func addToCachedArticlesForFeedID(_ feedID: String, _ articles: Set<LocalArticle>) {
|
||||
//
|
||||
// if let cachedArticles = cachedArticlesForFeedID(feedID) {
|
||||
// replaceCachedArticlesForFeedID(feedID, cachedArticles.union(articles))
|
||||
// }
|
||||
// else {
|
||||
// replaceCachedArticlesForFeedID(feedID, articles)
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func addToCachedArticlesForFeedID(_ articles: Set<LocalArticle>) {
|
||||
//
|
||||
// for oneArticle in articles {
|
||||
// addToCachedArticlesForFeedID(oneArticle.feedID, Set([oneArticle]))
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// func replaceCachedArticlesForFeedID(_ feedID: String, _ articles: Set<LocalArticle>) {
|
||||
//
|
||||
// articlesByFeedID[feedID] = articles
|
||||
// }
|
||||
//
|
||||
//}
|
52
Frameworks/Database/ArticlesManager.swift
Normal file
52
Frameworks/Database/ArticlesManager.swift
Normal file
@ -0,0 +1,52 @@
|
||||
//
|
||||
// ArticlesManager.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 5/9/16.
|
||||
// Copyright © 2016 Ranchero Software, LLC. All rights reserved.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import Data
|
||||
|
||||
final class ArticlesManager {
|
||||
|
||||
private let cachedArticles: NSMapTable<NSString, Article> = NSMapTable.weakToWeakObjects()
|
||||
|
||||
func uniquedArticles(_ fetchedArticles: Set<Article>, statusesManager: StatusesManager) -> Set<Article> {
|
||||
|
||||
var articles = Set<Article>()
|
||||
|
||||
for oneArticle in fetchedArticles {
|
||||
|
||||
assert(oneArticle.status != nil)
|
||||
|
||||
if let existingArticle = cachedArticle(oneArticle.articleID) {
|
||||
articles.insert(existingArticle)
|
||||
}
|
||||
else {
|
||||
cacheArticle(oneArticle)
|
||||
articles.insert(oneArticle)
|
||||
}
|
||||
}
|
||||
|
||||
statusesManager.attachCachedStatuses(articles)
|
||||
|
||||
return articles
|
||||
}
|
||||
|
||||
func cachedArticle(_ articleID: String) -> Article? {
|
||||
|
||||
return cachedArticles.object(forKey: articleID as NSString)
|
||||
}
|
||||
|
||||
func cacheArticle(_ article: Article) {
|
||||
|
||||
cachedArticles.setObject(article, forKey: article.articleID as NSString)
|
||||
}
|
||||
|
||||
func cacheArticles(_ articles: Set<Article>) {
|
||||
|
||||
articles.forEach { cacheArticle($0) }
|
||||
}
|
||||
}
|
@ -8,8 +8,8 @@
|
||||
|
||||
import Foundation
|
||||
import RSCore
|
||||
//import RSParser
|
||||
import RSDatabase
|
||||
import RSParser
|
||||
import Data
|
||||
|
||||
let sqlLogging = false
|
||||
@ -20,7 +20,7 @@ func logSQL(_ sql: String) {
|
||||
}
|
||||
}
|
||||
|
||||
typealias LocalArticleResultBlock = (Set<LocalArticle>) -> Void
|
||||
typealias ArticleResultBlock = (Set<Article>) -> Void
|
||||
|
||||
private let articlesTableName = "articles"
|
||||
|
||||
@ -28,43 +28,34 @@ final class LocalDatabase {
|
||||
|
||||
fileprivate let queue: RSDatabaseQueue
|
||||
private let databaseFile: String
|
||||
fileprivate let statusesManager: LocalStatusesManager
|
||||
fileprivate let articleCache: LocalArticleCache
|
||||
fileprivate let statusesManager: StatusesManager
|
||||
fileprivate let articleCache = ArticlesManager()
|
||||
fileprivate var articleArrivalCutoffDate = NSDate.rs_dateWithNumberOfDays(inThePast: 3 * 31)!
|
||||
fileprivate let minimumNumberOfArticles = 10
|
||||
fileprivate weak var delegate: AccountDelegate?
|
||||
|
||||
var account: LocalAccount!
|
||||
|
||||
init(databaseFile: String) {
|
||||
init(databaseFile: String, delegate: AccountDelegate) {
|
||||
|
||||
self.delegate = delegate
|
||||
self.databaseFile = databaseFile
|
||||
self.queue = RSDatabaseQueue(filepath: databaseFile, excludeFromBackup: false)
|
||||
self.statusesManager = LocalStatusesManager(queue: self.queue)
|
||||
self.articleCache = LocalArticleCache(statusesManager: self.statusesManager)
|
||||
self.statusesManager = StatusesManager(queue: self.queue)
|
||||
|
||||
let createStatementsPath = Bundle(for: type(of: self)).path(forResource: "LocalCreateStatements", ofType: "sql")!
|
||||
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: API
|
||||
|
||||
func startup() {
|
||||
|
||||
assert(account != nil)
|
||||
// deleteOldArticles(articleIDsInFeeds)
|
||||
}
|
||||
|
||||
// MARK: Fetching Articles
|
||||
|
||||
func fetchArticlesForFeed(_ feed: LocalFeed) -> Set<LocalArticle> {
|
||||
func fetchArticlesForFeed(_ feed: Feed) -> Set<Article> {
|
||||
|
||||
// if let articles = articleCache.cachedArticlesForFeedID(feed.feedID) {
|
||||
// return articles
|
||||
// }
|
||||
|
||||
var fetchedArticles = Set<LocalArticle>()
|
||||
var fetchedArticles = Set<Article>()
|
||||
let feedID = feed.feedID
|
||||
|
||||
queue.fetchSync { (database: FMDatabase!) -> Void in
|
||||
@ -76,7 +67,7 @@ final class LocalDatabase {
|
||||
return filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count])
|
||||
}
|
||||
|
||||
func fetchArticlesForFeedAsync(_ feed: LocalFeed, _ resultBlock: @escaping LocalArticleResultBlock) {
|
||||
func fetchArticlesForFeedAsync(_ feed: Feed, _ resultBlock: @escaping ArticleResultBlock) {
|
||||
|
||||
// if let articles = articleCache.cachedArticlesForFeedID(feed.feedID) {
|
||||
// resultBlock(articles)
|
||||
@ -138,18 +129,18 @@ final class LocalDatabase {
|
||||
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesForFolder(_ folder: LocalFolder) -> Set<LocalArticle> {
|
||||
func fetchUnreadArticlesForFolder(_ folder: Folder) -> Set<Article> {
|
||||
|
||||
return fetchUnreadArticlesForFeedIDs(Array(folder.flattenedFeedIDs))
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesForFeedIDs(_ feedIDs: [String]) -> Set<LocalArticle> {
|
||||
func fetchUnreadArticlesForFeedIDs(_ feedIDs: [String]) -> Set<Article> {
|
||||
|
||||
if feedIDs.isEmpty {
|
||||
return Set<LocalArticle>()
|
||||
}
|
||||
|
||||
var fetchedArticles = Set<LocalArticle>()
|
||||
var fetchedArticles = Set<Article>()
|
||||
var counts = [String: Int]()
|
||||
|
||||
queue.fetchSync { (database: FMDatabase!) -> Void in
|
||||
@ -191,14 +182,14 @@ final class LocalDatabase {
|
||||
|
||||
// MARK: Updating Articles
|
||||
|
||||
func updateFeedWithParsedFeed(_ feed: LocalFeed, parsedFeed: ParsedFeed, completionHandler: @escaping RSVoidCompletionBlock) {
|
||||
func updateFeedWithParsedFeed(_ feed: Feed, parsedFeed: ParsedFeed, completionHandler: @escaping RSVoidCompletionBlock) {
|
||||
|
||||
if parsedFeed.items.isEmpty {
|
||||
completionHandler()
|
||||
return
|
||||
}
|
||||
|
||||
let parsedArticlesDictionary = self.articlesDictionary(parsedFeed.articles as NSSet) as! [String: ParsedItem]
|
||||
let parsedArticlesDictionary = self.articlesDictionary(parsedFeed.items as NSSet) as! [String: ParsedItem]
|
||||
|
||||
fetchArticlesForFeedAsync(feed) { (articles) -> Void in
|
||||
|
||||
@ -211,7 +202,7 @@ final class LocalDatabase {
|
||||
|
||||
func markArticles(_ articles: NSSet, statusKey: ArticleStatusKey, flag: Bool) {
|
||||
|
||||
statusesManager.markArticles(articles as! Set<LocalArticle>, statusKey: statusKey, flag: flag)
|
||||
statusesManager.markArticles(articles as! Set<Article>, statusKey: statusKey, flag: flag)
|
||||
}
|
||||
}
|
||||
|
||||
@ -221,7 +212,7 @@ private extension LocalDatabase {
|
||||
|
||||
// MARK: Saving Articles
|
||||
|
||||
func saveUpdatedAndNewArticles(_ articleChanges: Set<NSDictionary>, newArticles: Set<LocalArticle>) {
|
||||
func saveUpdatedAndNewArticles(_ articleChanges: Set<NSDictionary>, newArticles: Set<Article>) {
|
||||
|
||||
if articleChanges.isEmpty && newArticles.isEmpty {
|
||||
return
|
||||
@ -259,7 +250,7 @@ private extension LocalDatabase {
|
||||
|
||||
// MARK: Updating Articles
|
||||
|
||||
func updateArticles(_ articles: [String: LocalArticle], parsedArticles: [String: ParsedItem], feed: LocalFeed, completionHandler: @escaping RSVoidCompletionBlock) {
|
||||
func updateArticles(_ articles: [String: Article], parsedArticles: [String: ParsedItem], feed: Feed, completionHandler: @escaping RSVoidCompletionBlock) {
|
||||
|
||||
statusesManager.ensureStatusesForParsedArticles(Set(parsedArticles.values)) {
|
||||
|
||||
@ -282,7 +273,7 @@ private extension LocalDatabase {
|
||||
return d
|
||||
}
|
||||
|
||||
func updateExistingArticles(_ articles: [String: LocalArticle], _ parsedArticles: [String: ParsedItem]) -> Set<NSDictionary> {
|
||||
func updateExistingArticles(_ articles: [String: Article], _ parsedArticles: [String: ParsedItem]) -> Set<NSDictionary> {
|
||||
|
||||
var articleChanges = Set<NSDictionary>()
|
||||
|
||||
@ -299,14 +290,14 @@ private extension LocalDatabase {
|
||||
|
||||
// MARK: Creating Articles
|
||||
|
||||
func createNewArticlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<LocalArticle> {
|
||||
func createNewArticlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> {
|
||||
|
||||
return Set(parsedArticles.map { LocalArticle(account: account, feedID: feedID, parsedArticle: $0) })
|
||||
}
|
||||
|
||||
func articlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<LocalArticle> {
|
||||
func articlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> {
|
||||
|
||||
var localArticles = Set<LocalArticle>()
|
||||
var localArticles = Set<Article>()
|
||||
|
||||
for oneParsedArticle in parsedArticles {
|
||||
let oneLocalArticle = LocalArticle(account: self.account, feedID: feedID, parsedArticle: oneParsedArticle)
|
||||
@ -316,7 +307,7 @@ private extension LocalDatabase {
|
||||
return localArticles
|
||||
}
|
||||
|
||||
func createNewArticles(_ existingArticles: [String: LocalArticle], parsedArticles: [String: ParsedItem], feedID: String) -> Set<LocalArticle> {
|
||||
func createNewArticles(_ existingArticles: [String: Article], parsedArticles: [String: ParsedItem], feedID: String) -> Set<Article> {
|
||||
|
||||
let newParsedArticles = parsedArticlesMinusExistingArticles(parsedArticles, existingArticles: existingArticles)
|
||||
let newArticles = createNewArticlesWithParsedArticles(newParsedArticles, feedID: feedID)
|
||||
@ -326,7 +317,7 @@ private extension LocalDatabase {
|
||||
return newArticles
|
||||
}
|
||||
|
||||
func parsedArticlesMinusExistingArticles(_ parsedArticles: [String: ParsedItem], existingArticles: [String: LocalArticle]) -> Set<ParsedItem> {
|
||||
func parsedArticlesMinusExistingArticles(_ parsedArticles: [String: ParsedItem], existingArticles: [String: Article]) -> Set<ParsedItem> {
|
||||
|
||||
var result = Set<ParsedItem>()
|
||||
|
||||
@ -343,7 +334,7 @@ private extension LocalDatabase {
|
||||
|
||||
// MARK: Fetching Articles
|
||||
|
||||
func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]?) -> Set<LocalArticle> {
|
||||
func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]?) -> Set<Article> {
|
||||
|
||||
let sql = "select * from articles natural join statuses where \(whereClause);"
|
||||
logSQL(sql)
|
||||
@ -355,14 +346,14 @@ private extension LocalDatabase {
|
||||
return Set<LocalArticle>()
|
||||
}
|
||||
|
||||
func articlesWithResultSet(_ resultSet: FMResultSet) -> Set<LocalArticle> {
|
||||
func articlesWithResultSet(_ resultSet: FMResultSet) -> Set<Article> {
|
||||
|
||||
var fetchedArticles = Set<LocalArticle>()
|
||||
var fetchedArticles = Set<Article>()
|
||||
|
||||
while (resultSet.next()) {
|
||||
|
||||
if let oneArticle = LocalArticle(account: self.account, row: resultSet) {
|
||||
oneArticle.status = LocalArticleStatus(row: resultSet)
|
||||
if let oneArticle = Article(account: self.account, row: resultSet) {
|
||||
oneArticle.status = ArticleStatus(row: resultSet)
|
||||
fetchedArticles.insert(oneArticle)
|
||||
}
|
||||
}
|
||||
@ -370,7 +361,7 @@ private extension LocalDatabase {
|
||||
return fetchedArticles
|
||||
}
|
||||
|
||||
func fetchArticlesForFeedID(_ feedID: String, database: FMDatabase) -> Set<LocalArticle> {
|
||||
func fetchArticlesForFeedID(_ feedID: String, database: FMDatabase) -> Set<Article> {
|
||||
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject])
|
||||
}
|
||||
@ -430,7 +421,7 @@ private extension LocalDatabase {
|
||||
|
||||
// MARK: Filtering out old articles
|
||||
|
||||
func articleIsOlderThanCutoffDate(_ article: LocalArticle) -> Bool {
|
||||
func articleIsOlderThanCutoffDate(_ article: Article) -> Bool {
|
||||
|
||||
if let dateArrived = article.status?.dateArrived {
|
||||
return dateArrived < articleArrivalCutoffDate
|
||||
@ -438,12 +429,12 @@ private extension LocalDatabase {
|
||||
return false
|
||||
}
|
||||
|
||||
func articleShouldBeSavedForever(_ article: LocalArticle) -> Bool {
|
||||
func articleShouldBeSavedForever(_ article: Article) -> Bool {
|
||||
|
||||
return article.status.starred
|
||||
}
|
||||
|
||||
func articleShouldAppearToUser(_ article: LocalArticle, _ numberOfArticlesInFeed: Int) -> Bool {
|
||||
func articleShouldAppearToUser(_ article: Article, _ numberOfArticlesInFeed: Int) -> Bool {
|
||||
|
||||
if numberOfArticlesInFeed <= minimumNumberOfArticles {
|
||||
return true
|
||||
@ -453,9 +444,9 @@ private extension LocalDatabase {
|
||||
|
||||
private static let minimumNumberOfArticlesInFeed = 10
|
||||
|
||||
func filteredArticles(_ articles: Set<LocalArticle>, feedCounts: [String: Int]) -> Set<LocalArticle> {
|
||||
func filteredArticles(_ articles: Set<Article>, feedCounts: [String: Int]) -> Set<Article> {
|
||||
|
||||
var articlesSet = Set<LocalArticle>()
|
||||
var articlesSet = Set<Article>()
|
||||
|
||||
for oneArticle in articles {
|
||||
if let feedCount = feedCounts[oneArticle.feedID], articleShouldAppearToUser(oneArticle, feedCount) {
|
||||
@ -469,12 +460,12 @@ private extension LocalDatabase {
|
||||
|
||||
typealias FeedCountCallback = (Int) -> Void
|
||||
|
||||
func feedIDsFromArticles(_ articles: Set<LocalArticle>) -> Set<String> {
|
||||
func feedIDsFromArticles(_ articles: Set<Article>) -> Set<String> {
|
||||
|
||||
return Set(articles.map { $0.feedID })
|
||||
}
|
||||
|
||||
func deletePossibleOldArticles(_ articles: Set<LocalArticle>) {
|
||||
func deletePossibleOldArticles(_ articles: Set<Article>) {
|
||||
|
||||
let feedIDs = feedIDsFromArticles(articles)
|
||||
if feedIDs.isEmpty {
|
||||
@ -506,7 +497,7 @@ private extension LocalDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
func deleteOldArticlesInFeed(_ feed: LocalFeed) {
|
||||
func deleteOldArticlesInFeed(_ feed: Feed) {
|
||||
|
||||
numberOfArticlesInFeedID(feed.feedID) { (numberOfArticlesInFeed) in
|
||||
|
||||
|
@ -9,8 +9,9 @@
|
||||
/* Begin PBXBuildFile section */
|
||||
844BEE411F0AB3AB004AB7CD /* Database.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 844BEE371F0AB3AA004AB7CD /* Database.framework */; };
|
||||
844BEE461F0AB3AB004AB7CD /* DatabaseTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 844BEE451F0AB3AB004AB7CD /* DatabaseTests.swift */; };
|
||||
846146271F0ABC7B00870CB3 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846146241F0ABC7400870CB3 /* RSParser.framework */; };
|
||||
84E156EA1F0AB80500F8CC05 /* Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E156E91F0AB80500F8CC05 /* Database.swift */; };
|
||||
84E156EC1F0AB80E00F8CC05 /* ArticleCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E156EB1F0AB80E00F8CC05 /* ArticleCache.swift */; };
|
||||
84E156EC1F0AB80E00F8CC05 /* ArticlesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E156EB1F0AB80E00F8CC05 /* ArticlesManager.swift */; };
|
||||
84E156EE1F0AB81400F8CC05 /* StatusesManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84E156ED1F0AB81400F8CC05 /* StatusesManager.swift */; };
|
||||
84E156F01F0AB81F00F8CC05 /* CreateStatements.sql in Resources */ = {isa = PBXBuildFile; fileRef = 84E156EF1F0AB81F00F8CC05 /* CreateStatements.sql */; };
|
||||
84E156FD1F0AB86100F8CC05 /* Data.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84E156F81F0AB83600F8CC05 /* Data.framework */; };
|
||||
@ -26,6 +27,20 @@
|
||||
remoteGlobalIDString = 844BEE361F0AB3AA004AB7CD;
|
||||
remoteInfo = Database;
|
||||
};
|
||||
846146231F0ABC7400870CB3 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 84FF5F841EFA285800C15A01;
|
||||
remoteInfo = RSParser;
|
||||
};
|
||||
846146251F0ABC7400870CB3 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */;
|
||||
proxyType = 2;
|
||||
remoteGlobalIDString = 84FF5F8D1EFA285800C15A01;
|
||||
remoteInfo = RSParserTests;
|
||||
};
|
||||
84E156F71F0AB83600F8CC05 /* PBXContainerItemProxy */ = {
|
||||
isa = PBXContainerItemProxy;
|
||||
containerPortal = 84E156F11F0AB83600F8CC05 /* Data.xcodeproj */;
|
||||
@ -68,9 +83,10 @@
|
||||
844BEE401F0AB3AB004AB7CD /* DatabaseTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = DatabaseTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
844BEE451F0AB3AB004AB7CD /* DatabaseTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseTests.swift; sourceTree = "<group>"; };
|
||||
844BEE471F0AB3AB004AB7CD /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSParser.xcodeproj; path = ../RSParser/RSParser.xcodeproj; sourceTree = "<group>"; };
|
||||
84E156E81F0AB75600F8CC05 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
84E156E91F0AB80500F8CC05 /* Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Database.swift; sourceTree = "<group>"; };
|
||||
84E156EB1F0AB80E00F8CC05 /* ArticleCache.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticleCache.swift; sourceTree = "<group>"; };
|
||||
84E156EB1F0AB80E00F8CC05 /* ArticlesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ArticlesManager.swift; sourceTree = "<group>"; };
|
||||
84E156ED1F0AB81400F8CC05 /* StatusesManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StatusesManager.swift; sourceTree = "<group>"; };
|
||||
84E156EF1F0AB81F00F8CC05 /* CreateStatements.sql */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = CreateStatements.sql; sourceTree = "<group>"; };
|
||||
84E156F11F0AB83600F8CC05 /* Data.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = Data.xcodeproj; path = ../Data/Data.xcodeproj; sourceTree = "<group>"; };
|
||||
@ -83,6 +99,7 @@
|
||||
isa = PBXFrameworksBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
846146271F0ABC7B00870CB3 /* RSParser.framework in Frameworks */,
|
||||
84E1570C1F0AB8A500F8CC05 /* RSDatabase.framework in Frameworks */,
|
||||
84E156FF1F0AB86700F8CC05 /* RSCore.framework in Frameworks */,
|
||||
84E156FD1F0AB86100F8CC05 /* Data.framework in Frameworks */,
|
||||
@ -104,7 +121,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
84E156E91F0AB80500F8CC05 /* Database.swift */,
|
||||
84E156EB1F0AB80E00F8CC05 /* ArticleCache.swift */,
|
||||
84E156EB1F0AB80E00F8CC05 /* ArticlesManager.swift */,
|
||||
84E156ED1F0AB81400F8CC05 /* StatusesManager.swift */,
|
||||
84E156EF1F0AB81F00F8CC05 /* CreateStatements.sql */,
|
||||
84E156E81F0AB75600F8CC05 /* Info.plist */,
|
||||
@ -132,6 +149,15 @@
|
||||
path = DatabaseTests;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
8461461F1F0ABC7300870CB3 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
846146241F0ABC7400870CB3 /* RSParser.framework */,
|
||||
846146261F0ABC7400870CB3 /* RSParserTests.xctest */,
|
||||
);
|
||||
name = Products;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
84E156F21F0AB83600F8CC05 /* Products */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@ -145,6 +171,7 @@
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
84E156FE1F0AB86700F8CC05 /* RSCore.framework */,
|
||||
8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */,
|
||||
84E157001F0AB89B00F8CC05 /* RSDatabase.xcodeproj */,
|
||||
84E156F11F0AB83600F8CC05 /* Data.xcodeproj */,
|
||||
);
|
||||
@ -252,6 +279,10 @@
|
||||
ProductGroup = 84E157011F0AB89B00F8CC05 /* Products */;
|
||||
ProjectRef = 84E157001F0AB89B00F8CC05 /* RSDatabase.xcodeproj */;
|
||||
},
|
||||
{
|
||||
ProductGroup = 8461461F1F0ABC7300870CB3 /* Products */;
|
||||
ProjectRef = 8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */;
|
||||
},
|
||||
);
|
||||
projectRoot = "";
|
||||
targets = (
|
||||
@ -262,6 +293,20 @@
|
||||
/* End PBXProject section */
|
||||
|
||||
/* Begin PBXReferenceProxy section */
|
||||
846146241F0ABC7400870CB3 /* RSParser.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
path = RSParser.framework;
|
||||
remoteRef = 846146231F0ABC7400870CB3 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
846146261F0ABC7400870CB3 /* RSParserTests.xctest */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.cfbundle;
|
||||
path = RSParserTests.xctest;
|
||||
remoteRef = 846146251F0ABC7400870CB3 /* PBXContainerItemProxy */;
|
||||
sourceTree = BUILT_PRODUCTS_DIR;
|
||||
};
|
||||
84E156F81F0AB83600F8CC05 /* Data.framework */ = {
|
||||
isa = PBXReferenceProxy;
|
||||
fileType = wrapper.framework;
|
||||
@ -323,7 +368,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
84E156EA1F0AB80500F8CC05 /* Database.swift in Sources */,
|
||||
84E156EC1F0AB80E00F8CC05 /* ArticleCache.swift in Sources */,
|
||||
84E156EC1F0AB80E00F8CC05 /* ArticlesManager.swift in Sources */,
|
||||
84E156EE1F0AB81400F8CC05 /* StatusesManager.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
|
@ -1,5 +1,5 @@
|
||||
//
|
||||
// LocalStatusesManager.swift
|
||||
//
|
||||
// StatusesManager.swift
|
||||
// Evergreen
|
||||
//
|
||||
// Created by Brent Simmons on 5/8/16.
|
||||
@ -9,12 +9,12 @@
|
||||
import Foundation
|
||||
import RSCore
|
||||
import RSDatabase
|
||||
//import RSParser
|
||||
import RSParser
|
||||
import Data
|
||||
|
||||
final class LocalStatusesManager {
|
||||
final class StatusesManager {
|
||||
|
||||
var cachedStatuses = [String: LocalArticleStatus]()
|
||||
var cachedStatuses = [String: ArticleStatus]()
|
||||
let queue: RSDatabaseQueue
|
||||
|
||||
init(queue: RSDatabaseQueue) {
|
||||
@ -22,30 +22,29 @@ final class LocalStatusesManager {
|
||||
self.queue = queue
|
||||
}
|
||||
|
||||
func markArticles(_ articles: Set<LocalArticle>, statusKey: ArticleStatusKey, flag: Bool) {
|
||||
func markArticles(_ articles: Set<Article>, statusKey: ArticleStatusKey, flag: Bool) {
|
||||
|
||||
assertNoMissingStatuses(articles)
|
||||
let statusArray = articles.map { $0.status! as! LocalArticleStatus }
|
||||
let statuses = Set(statusArray)
|
||||
let statuses = Set(articles.flatMap { $0.status })
|
||||
markArticleStatuses(statuses, statusKey: statusKey, flag: flag)
|
||||
}
|
||||
|
||||
func attachCachedUniqueStatuses(_ articles: Set<LocalArticle>) {
|
||||
func attachCachedStatuses(_ articles: Set<Article>) {
|
||||
|
||||
articles.forEach { (oneLocalArticle) in
|
||||
articles.forEach { (oneArticle) in
|
||||
|
||||
if let cachedStatus = cachedStatusForArticleID(oneLocalArticle.articleID) {
|
||||
oneLocalArticle.status = cachedStatus
|
||||
if let cachedStatus = cachedStatusForArticleID(oneArticle.articleID) {
|
||||
oneArticle.status = cachedStatus
|
||||
}
|
||||
else if let oneLocalArticleStatus = oneLocalArticle.status as? LocalArticleStatus {
|
||||
cacheStatus(oneLocalArticleStatus)
|
||||
else if let oneArticleStatus = oneArticle.status as? ArticleStatus {
|
||||
cacheStatus(oneArticleStatus)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func ensureStatusesForParsedArticles(_ parsedArticles: [ParsedItem], _ callback: @escaping RSVoidCompletionBlock) {
|
||||
|
||||
var articleIDs = Set(parsedArticles.map { $0.databaseID })
|
||||
var articleIDs = Set(parsedArticles.map { $0.articleID })
|
||||
articleIDs = articleIDsMissingStatuses(articleIDs)
|
||||
if articleIDs.isEmpty {
|
||||
callback()
|
||||
@ -67,7 +66,7 @@ final class LocalStatusesManager {
|
||||
}
|
||||
}
|
||||
|
||||
func assertNoMissingStatuses(_ articles: Set<LocalArticle>) {
|
||||
func assertNoMissingStatuses(_ articles: Set<Article>) {
|
||||
|
||||
for oneArticle in articles {
|
||||
if oneArticle.status == nil {
|
||||
@ -82,11 +81,11 @@ final class LocalStatusesManager {
|
||||
|
||||
private let statusesTableName = "statuses"
|
||||
|
||||
private extension LocalStatusesManager {
|
||||
private extension StatusesManager {
|
||||
|
||||
// MARK: Marking
|
||||
|
||||
func markArticleStatuses(_ statuses: Set<LocalArticleStatus>, statusKey: ArticleStatusKey, flag: Bool) {
|
||||
func markArticleStatuses(_ statuses: Set<ArticleStatus>, statusKey: ArticleStatusKey, flag: Bool) {
|
||||
|
||||
// Ignore the statuses where status.[statusKey] == flag. Update the remainder and save in database.
|
||||
|
||||
@ -107,25 +106,25 @@ private extension LocalStatusesManager {
|
||||
|
||||
// MARK: Fetching
|
||||
|
||||
func fetchStatusesForArticleIDs(_ articleIDs: Set<String>, database: FMDatabase) -> Set<LocalArticleStatus> {
|
||||
func fetchStatusesForArticleIDs(_ articleIDs: Set<String>, database: FMDatabase) -> Set<ArticleStatus> {
|
||||
|
||||
guard !articleIDs.isEmpty else {
|
||||
return Set<LocalArticleStatus>()
|
||||
return Set<ArticleStatus>()
|
||||
}
|
||||
|
||||
guard let resultSet = database.rs_selectRowsWhereKey(articleIDKey, inValues: Array(articleIDs), tableName: statusesTableName) else {
|
||||
return Set<LocalArticleStatus>()
|
||||
return Set<ArticleStatus>()
|
||||
}
|
||||
|
||||
return localArticleStatusesWithResultSet(resultSet)
|
||||
return articleStatusesWithResultSet(resultSet)
|
||||
}
|
||||
|
||||
func localArticleStatusesWithResultSet(_ resultSet: FMResultSet) -> Set<LocalArticleStatus> {
|
||||
func articleStatusesWithResultSet(_ resultSet: FMResultSet) -> Set<ArticleStatus> {
|
||||
|
||||
var statuses = Set<LocalArticleStatus>()
|
||||
var statuses = Set<ArticleStatus>()
|
||||
|
||||
while(resultSet.next()) {
|
||||
if let oneArticleStatus = LocalArticleStatus(row: resultSet) {
|
||||
if let oneArticleStatus = ArticleStatus(row: resultSet) {
|
||||
statuses.insert(oneArticleStatus)
|
||||
}
|
||||
}
|
||||
@ -135,7 +134,7 @@ private extension LocalStatusesManager {
|
||||
|
||||
// MARK: Saving
|
||||
|
||||
func saveStatuses(_ statuses: Set<LocalArticleStatus>) {
|
||||
func saveStatuses(_ statuses: Set<ArticleStatus>) {
|
||||
|
||||
let statusArray = statuses.map { (oneStatus) -> NSDictionary in
|
||||
return oneStatus.databaseDictionary
|
||||
@ -164,7 +163,7 @@ private extension LocalStatusesManager {
|
||||
|
||||
let now = Date()
|
||||
let statuses = articleIDs.map { (oneArticleID) -> LocalArticleStatus in
|
||||
return LocalArticleStatus(articleID: oneArticleID, read: false, starred: false, userDeleted: false, dateArrived: now)
|
||||
return ArticleStatus(articleID: oneArticleID, read: false, starred: false, userDeleted: false, dateArrived: now)
|
||||
}
|
||||
cacheStatuses(Set(statuses))
|
||||
|
||||
@ -181,17 +180,17 @@ private extension LocalStatusesManager {
|
||||
|
||||
// MARK: Cache
|
||||
|
||||
func cachedStatusForArticleID(_ articleID: String) -> LocalArticleStatus? {
|
||||
func cachedStatusForArticleID(_ articleID: String) -> ArticleStatus? {
|
||||
|
||||
return cachedStatuses[articleID]
|
||||
}
|
||||
|
||||
func cacheStatus(_ status: LocalArticleStatus) {
|
||||
func cacheStatus(_ status: ArticleStatus) {
|
||||
|
||||
cacheStatuses(Set([status]))
|
||||
}
|
||||
|
||||
func cacheStatuses(_ statuses: Set<LocalArticleStatus>) {
|
||||
func cacheStatuses(_ statuses: Set<ArticleStatus>) {
|
||||
|
||||
statuses.forEach { (oneStatus) in
|
||||
if let _ = cachedStatuses[oneStatus.articleID] {
|
||||
|
@ -8,7 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
|
||||
public struct ParsedItem {
|
||||
public struct ParsedItem: Hashable {
|
||||
|
||||
public let uniqueID: String
|
||||
public let feedURL: String
|
||||
@ -25,7 +25,8 @@ public struct ParsedItem {
|
||||
public let authors: [ParsedAuthor]?
|
||||
public let tags: [String]?
|
||||
public let attachments: [ParsedAttachment]?
|
||||
|
||||
public let hashValue: Int
|
||||
|
||||
init(uniqueID: String, feedURL: String, url: String?, externalURL: String?, title: String?, contentHTML: String?, contentText: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: [ParsedAuthor]?, tags: [String]?, attachments: [ParsedAttachment]?) {
|
||||
|
||||
self.uniqueID = uniqueID
|
||||
@ -43,6 +44,12 @@ public struct ParsedItem {
|
||||
self.authors = authors
|
||||
self.tags = tags
|
||||
self.attachments = attachments
|
||||
self.hashValue = uniqueID.hashValue
|
||||
}
|
||||
|
||||
public static func ==(lhs: ParsedItem, rhs: ParsedItem) -> Bool {
|
||||
|
||||
return lhs.hashValue == rhs.hashValue && lhs.uniqueID == rhs.uniqueID && lhs.feedURL == rhs.feedURL
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user