Work on getting Database.framework to build.

This commit is contained in:
Brent Simmons 2017-07-03 11:20:14 -07:00
parent 0379e5f525
commit d47c60e6de
9 changed files with 205 additions and 198 deletions

View File

@ -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

View File

@ -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
}
}

View File

@ -8,7 +8,7 @@
import Foundation
public protocol AccountDelegate {
public protocol AccountDelegate: class {
func canAddItem(_ item: AnyObject, toContainer: Container) -> Bool

View File

@ -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
// }
//
//}

View 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) }
}
}

View File

@ -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

View File

@ -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;

View File

@ -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] {

View File

@ -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
}
}