Make ParsedFeed.items a Set<ParsedItem>. Fix some build errors in Database.framework.

This commit is contained in:
Brent Simmons 2017-09-09 18:46:58 -07:00
parent 7680760537
commit 7415131e8d
12 changed files with 69 additions and 71 deletions

View File

@ -8,9 +8,17 @@
import Foundation
// This is used by an Account that needs to store extra info.
// Its stored as a binary plist in the database.
public struct AccountInfo: Equatable {
var dictionary: [String: AnyObject]?
var plist: [String: AnyObject]?
init(plist: [String: AnyObject]) {
self.plist = plist
}
public static func ==(lhs: AccountInfo, rhs: AccountInfo) -> Bool {
@ -18,8 +26,6 @@ public struct AccountInfo: Equatable {
}
}
// AccountInfo is a plist-compatible dictionary thats stored as a binary plist in the database.
//func accountInfoWithRow(_ row: FMResultSet) -> AccountInfo? {
//
// guard let rawAccountInfo = row.data(forColumn: DatabaseKey.accountInfo) else {

View File

@ -18,7 +18,7 @@ public final class Feed: DisplayNameProvider, Hashable {
public var name: String?
public var editedName: String?
public var articles = Set<Article>()
public var accountInfo: [String: Any]? //If account needs to store more data
public var accountInfo: AccountInfo? //If account needs to store more data
public let hashValue: Int
public var nameForDisplay: String {

View File

@ -31,8 +31,7 @@ final class ArticlesTable: DatabaseTable {
self.name = name
self.accountID = accountID
self.queue = queue
let statusesTable = StatusesTable(queue: queue)
self.statusesTable = StatusesTable(queue: queue)
let authorsTable = AuthorsTable(name: DatabaseTableName.authors)
self.authorsLookupTable = DatabaseLookupTable(name: DatabaseTableName.authorsLookup, objectIDKey: DatabaseKey.articleID, relatedObjectIDKey: DatabaseKey.authorID, relatedTable: authorsTable, relationshipName: RelationshipName.authors)
@ -96,17 +95,16 @@ final class ArticlesTable: DatabaseTable {
let feedID = feed.feedID
let parsedItemArticleIDs = Set(parsedFeed.items.map { $0.databaseIdentifierWithFeed(feed) })
let parsedItemsDictionary = parsedFeed.itemsDictionary(with: feed)
statusesTable.ensureStatusesForArticleIDs(parsedItemArticleIDs) { // 1
statusesTable.ensureStatusesForArticleIDs(parsedItemArticleIDs) { (statusesDictionary) in // 1
let filteredParsedItems = self.filterParsedItems(parsedItemsDictionary) // 2
let filteredParsedItems = self.filterParsedItems(Set(parsedFeed.items), statusesDictionary) // 2
if filteredParsedItems.isEmpty {
completion(nil, nil)
return
}
queue.update{ (database) in
self.queue.update{ (database) in
let fetchedArticles = self.fetchArticlesForFeedID(feedID, withLimits: false, database: database) //3
let fetchedArticlesDictionary = fetchedArticles.dictionary()
@ -196,10 +194,7 @@ private extension ArticlesTable {
func articleWithRow(_ row: FMResultSet) -> Article? {
guard let account = account else {
return nil
}
guard let article = Article(row: row, account: account) else {
guard let article = Article(row: row, accountID: accountID) else {
return nil
}
@ -360,23 +355,18 @@ private extension ArticlesTable {
return status.dateArrived < maximumArticleCutoffDate
}
func filterParsedItems(_ parsedItems: [String: ParsedItem], _ statuses: [String: ArticleStatus]) -> [String: ParsedItem] {
func filterParsedItems(_ parsedItems: Set<ParsedItem>, _ statuses: [String: ArticleStatus]) -> Set<ParsedItem> {
// Drop parsedItems that we can ignore.
var d = [String: ParsedItem]()
for (articleID, parsedItem) in parsedItems {
return Set(parsedItems.filter{ (parsedItem) -> Bool in
let articleID = parsedItem.articleID
if let status = statuses[articleID] {
if statusIndicatesArticleIsIgnorable(status) {
continue
return !statusIndicatesArticleIsIgnorable(status)
}
}
d[articleID] = parsedItem
}
return d
assertionFailure("Expected a status for each parsedItem.")
return true
})
}
}

View File

@ -15,23 +15,24 @@ import Data
public typealias ArticleResultBlock = (Set<Article>) -> Void
public typealias UnreadCountTable = [String: Int] // feedID: unreadCount
public typealias UnreadCountCompletionBlock = (UnreadCountTable) -> Void //feedID: unreadCount
typealias UpdateArticlesWithFeedCompletionBlock = (Set<Article>, Set<Article>) -> Void
public typealias UpdateArticlesWithFeedCompletionBlock = (Set<Article>?, Set<Article>?) -> Void //newArticles, updateArticles
public final class Database {
private let accountID: String
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
public init(databaseFile: String) {
public init(databaseFile: String, accountID: String) {
self.account = account
self.accountID = accountID
self.databaseFile = databaseFile
self.queue = RSDatabaseQueue(filepath: databaseFile, excludeFromBackup: false)
self.articlesTable = ArticlesTable(name: DatabaseTableName.articles, account: account, queue: queue)
self.articlesTable = ArticlesTable(name: DatabaseTableName.articles, accountID: accountID, 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)
@ -65,7 +66,7 @@ public final class Database {
// MARK: - Updating Articles
public func update(feed: Feed, parsedFeed: ParsedFeed, completion: @escaping RSVoidCompletionBlock) {
public func update(feed: Feed, parsedFeed: ParsedFeed, completion: @escaping UpdateArticlesWithFeedCompletionBlock) {
return articlesTable.update(feed, parsedFeed, completion)
}

View File

@ -33,7 +33,7 @@ extension Article {
let bannerImageURL = row.string(forColumn: DatabaseKey.bannerImageURL)
let datePublished = row.date(forColumn: DatabaseKey.datePublished)
let dateModified = row.date(forColumn: DatabaseKey.dateModified)
let accountInfo: [String: Any]? = nil // TODO
let accountInfo: AccountInfo? = nil // TODO
self.init(accountID: accountID, articleID: articleID, feedID: feedID, uniqueID: uniqueID, title: title, contentHTML: contentHTML, contentText: contentText, url: url, externalURL: externalURL, summary: summary, imageURL: imageURL, bannerImageURL: bannerImageURL, datePublished: datePublished, dateModified: dateModified, authors: authors, tags: tags, attachments: attachments, accountInfo: accountInfo)
}
@ -125,7 +125,7 @@ extension Article {
return d
}
static func articlesWithParsedItems(_ parsedItems: [ParsedItem], _ accountID: String, _ feedID: String) -> Set<Article> {
static func articlesWithParsedItems(_ parsedItems: Set<ParsedItem>, _ accountID: String, _ feedID: String) -> Set<Article> {
return Set(parsedItems.map{ Article(parsedItem: $0, accountID: accountID, feedID: feedID) })
}

View File

@ -23,19 +23,6 @@ extension ParsedItem {
}
}
extension ParsedFeed {
func itemsDictionary(with feed: Feed) -> [String: ParsedItem] {
var d = [String: ParsedItem]()
for parsedItem in items {
let identifier = parsedItem.databaseIdentifierWithFeed(feed)
d[identifier] = parsedItem
}
return d
}
}

View File

@ -15,6 +15,8 @@ import Data
//
// 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);
typealias StatusesCompletionBlock = ([String: ArticleStatus]) -> Void // [articleID: Status]
final class StatusesTable: DatabaseTable {
let name = DatabaseTableName.statuses
@ -35,7 +37,7 @@ final class StatusesTable: DatabaseTable {
// MARK: Creating/Updating
func ensureStatusesForArticleIDs(_ articleIDs: Set<String>, _ completion: @escaping RSVoidCompletionBlock) {
func ensureStatusesForArticleIDs(_ articleIDs: Set<String>, _ completion: @escaping StatusesCompletionBlock) {
// Adds them to the cache if not cached.
@ -44,21 +46,21 @@ final class StatusesTable: DatabaseTable {
// Check cache.
let articleIDsMissingCachedStatus = articleIDsWithNoCachedStatus(articleIDs)
if articleIDsMissingCachedStatus.isEmpty {
completion()
completion(statusesDictionary(articleIDs))
return
}
// Check database.
fetchAndCacheStatusesForArticleIDs(articleIDsMissingCachedStatus) {
let articleIDsNeedingStatus = articleIDsWithNoCachedStatus(articleIDs)
let articleIDsNeedingStatus = self.articleIDsWithNoCachedStatus(articleIDs)
if articleIDsNeedingStatus.isEmpty {
completion()
completion(statusesDictionary(articleIDs))
return
}
// Create new statuses.
createAndSaveStatusesForArticleIDs(articleIDsNeedingStatus, completion)
self.createAndSaveStatusesForArticleIDs(articleIDsNeedingStatus, completion)
}
}
@ -95,16 +97,32 @@ private extension StatusesTable {
func articleIDsWithNoCachedStatus(_ articleIDs: Set<String>) -> Set<String> {
assert(Thread.isMainThread)
return Set(articleIDs.filter { cache[$0] == nil })
}
func statusesDictionary(_ articleIDs: Set<String>) -> [String: ArticleStatus] {
assert(Thread.isMainThread)
var d = [String: ArticleStatus]()
for articleID in articleIDs {
if let articleStatus = cache[articleID] {
d[articleID] = articleStatus
}
}
return d
}
// MARK: Creating
func saveStatuses(_ statuses: Set<ArticleStatus>) {
queue.update { (database) in
let statusArray = statuses.map { $0.databaseDictionary() }
insertRows(statusArray, insertType: .orIgnore, in: database)
self.insertRows(statusArray, insertType: .orIgnore, in: database)
}
}
@ -126,15 +144,15 @@ private extension StatusesTable {
func fetchAndCacheStatusesForArticleIDs(_ articleIDs: Set<String>, _ completion: @escaping RSVoidCompletionBlock) {
queue.fetch { (database) in
guard let resultSet = selectRowsWhere(key: DatabaseKey.articleID, inValues: Array(articleIDs), in: database) else {
guard let resultSet = self.selectRowsWhere(key: DatabaseKey.articleID, inValues: Array(articleIDs), in: database) else {
completion()
return
}
let statuses = resultSet.mapToSet(statusWithRow)
let statuses = resultSet.mapToSet(self.statusWithRow)
DispatchQueue.main.async {
cache.addIfNotCached(statuses)
self.cache.addIfNotCached(statuses)
completion()
}
}

View File

@ -81,11 +81,11 @@ private extension JSONFeedParser {
return hubs.isEmpty ? nil : hubs
}
static func parseItems(_ itemsArray: JSONArray, _ feedURL: String) -> [ParsedItem] {
static func parseItems(_ itemsArray: JSONArray, _ feedURL: String) -> Set<ParsedItem> {
return itemsArray.flatMap { (oneItemDictionary) -> ParsedItem? in
return Set(itemsArray.flatMap { (oneItemDictionary) -> ParsedItem? in
return parseItem(oneItemDictionary, feedURL)
}
})
}
static func parseItem(_ itemDictionary: JSONDictionary, _ feedURL: String) -> ParsedItem? {

View File

@ -58,12 +58,12 @@ public struct RSSInJSONParser {
private extension RSSInJSONParser {
static func parseItems(_ itemsObject: JSONArray, _ feedURL: String) -> [ParsedItem] {
static func parseItems(_ itemsObject: JSONArray, _ feedURL: String) -> Set<ParsedItem> {
return itemsObject.flatMap{ (oneItemDictionary) -> ParsedItem? in
return Set(itemsObject.flatMap{ (oneItemDictionary) -> ParsedItem? in
return parsedItemWithDictionary(oneItemDictionary, feedURL)
}
})
}
static func parsedItemWithDictionary(_ itemDictionary: JSONDictionary, _ feedURL: String) -> ParsedItem? {

View File

@ -21,9 +21,9 @@ public struct ParsedFeed {
public let authors: [ParsedAuthor]?
public let expired: Bool
public let hubs: [ParsedHub]?
public let items: [ParsedItem]
public let items: Set<ParsedItem>
init(type: FeedType, title: String?, homePageURL: String?, feedURL: String?, feedDescription: String?, nextURL: String?, iconURL: String?, faviconURL: String?, authors: [ParsedAuthor]?, expired: Bool, hubs: [ParsedHub]?, items:[ParsedItem]) {
init(type: FeedType, title: String?, homePageURL: String?, feedURL: String?, feedDescription: String?, nextURL: String?, iconURL: String?, faviconURL: String?, authors: [ParsedAuthor]?, expired: Bool, hubs: [ParsedHub]?, items: Set<ParsedItem>) {
self.type = type
self.title = title

View File

@ -24,15 +24,11 @@ struct RSParsedFeedTransformer {
private extension RSParsedFeedTransformer {
static func parsedItems(_ parsedArticles: Set<RSParsedArticle>) -> [ParsedItem] {
static func parsedItems(_ parsedArticles: Set<RSParsedArticle>) -> Set<ParsedItem> {
// Create [ParsedItem] from set of RSParsedArticle.
// Create Set<ParsedItem> from Set<RSParsedArticle>
var items = [ParsedItem]()
for oneParsedArticle in parsedArticles {
items += [parsedItem(oneParsedArticle)]
}
return items
return Set(parsedArticles.map(parsedItem))
}
static func parsedItem(_ parsedArticle: RSParsedArticle) -> ParsedItem {

Binary file not shown.