Decide on preliminary public API for Database.swift. Stub-out everything.

This commit is contained in:
Brent Simmons 2017-08-20 21:23:17 -07:00
parent c164c29cde
commit 9ddaaf5f5d
7 changed files with 405 additions and 376 deletions

View File

@ -39,7 +39,7 @@ public final class Article: Hashable {
} }
} }
init(account: Account, databaseID: String?, feedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: [Author]?, tags: Set<String>?, attachments: [Attachment]?, accountInfo: AccountInfo?) { init(account: Account, articleID: String?, feedID: String, uniqueID: String, title: String?, contentHTML: String?, contentText: String?, url: String?, externalURL: String?, summary: String?, imageURL: String?, bannerImageURL: String?, datePublished: Date?, dateModified: Date?, authors: [Author]?, tags: Set<String>?, attachments: [Attachment]?, accountInfo: AccountInfo?) {
self.account = account self.account = account
self.feedID = feedID self.feedID = feedID
@ -59,16 +59,14 @@ public final class Article: Hashable {
self.attachments = attachments self.attachments = attachments
self.accountInfo = accountInfo self.accountInfo = accountInfo
let _databaseID: String if let articleID = articleID {
if let databaseID = databaseID { self.articleID = articleID
_databaseID = databaseID
} }
else { else {
_databaseID = databaseIDWithString("\(feedID) \(uniqueID)") self.articleID = databaseIDWithString("\(feedID) \(uniqueID)")
} }
self.databaseID = _databaseID
self.hashValue = account.hashValue ^ _databaseID.hashValue self.hashValue = account.hashValue ^ self.articleID.hashValue
} }
public class func ==(lhs: Article, rhs: Article) -> Bool { public class func ==(lhs: Article, rhs: Article) -> Bool {

View File

@ -44,6 +44,6 @@ public struct Author: Hashable {
public static func ==(lhs: Author, rhs: Author) -> Bool { public static func ==(lhs: Author, rhs: Author) -> Bool {
return lhs.hashValue == rhs.hashValue && lhs.databaseID == rhs.databaseID return lhs.hashValue == rhs.hashValue && lhs.authorID == rhs.authorID
} }
} }

View File

@ -69,4 +69,5 @@ public struct RelationshipName {
static let authors = "authors" static let authors = "authors"
static let tags = "tags" static let tags = "tags"
static let attachments = "attachments"
} }

View File

@ -51,7 +51,7 @@ final class Database {
self.tagsLookupTable = DatabaseLookupTable(name: DatabaseTableName.tags, objectIDKey: DatabaseKey.articleID, relatedObjectIDKey: DatabaseKey.tagName, relatedTable: tagsTable, relationshipName: RelationshipName.tags) self.tagsLookupTable = DatabaseLookupTable(name: DatabaseTableName.tags, objectIDKey: DatabaseKey.articleID, relatedObjectIDKey: DatabaseKey.tagName, relatedTable: tagsTable, relationshipName: RelationshipName.tags)
let attachmentsTable = AttachmentsTable(name: DatabaseTableName.attachments) let attachmentsTable = AttachmentsTable(name: DatabaseTableName.attachments)
self.attachmentsLookupTable = DatabaseLookupTable(name: DatabaseTableName.tags, objectIDKey: DatabaseKey.articleID, relatedObjectIDKey: DatabaseKey.tagName, relatedTable: tagsTable, relationshipName: RelationshipName.tags) self.attachmentsLookupTable = DatabaseLookupTable(name: DatabaseTableName.attachmentsLookup, objectIDKey: DatabaseKey.articleID, relatedObjectIDKey: DatabaseKey.attachmentID, relatedTable: attachmentsTable, relationshipName: RelationshipName.attachments)
let createStatementsPath = Bundle(for: type(of: self)).path(forResource: "CreateStatements", 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) let createStatements = try! NSString(contentsOfFile: createStatementsPath, encoding: String.Encoding.utf8.rawValue)
@ -59,399 +59,407 @@ final class Database {
queue.vacuumIfNeeded() queue.vacuumIfNeeded()
} }
// MARK: Fetching Articles // MARK: - Fetching Articles
func fetchArticlesForFeed(_ feed: Feed) -> Set<Article> { func fetchArticlesForFeed(_ feed: Feed) -> Set<Article> {
var fetchedArticles = Set<Article>() return Set<Article>() // TODO
let feedID = feed.feedID // var fetchedArticles = Set<Article>()
// let feedID = feed.feedID
queue.fetchSync { (database: FMDatabase!) -> Void in //
// queue.fetchSync { (database: FMDatabase!) -> Void in
fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database) //
} // fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database)
// }
let articles = articleCache.uniquedArticles(fetchedArticles, statusesTable: statusesTable) //
return filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count]) // let articles = articleCache.uniquedArticles(fetchedArticles, statusesTable: statusesTable)
// return filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count])
} }
func fetchArticlesForFeedAsync(_ feed: Feed, _ resultBlock: @escaping ArticleResultBlock) { func fetchArticlesForFeedAsync(_ feed: Feed, _ resultBlock: @escaping ArticleResultBlock) {
let feedID = feed.feedID // let feedID = feed.feedID
//
queue.fetch { (database: FMDatabase!) -> Void in // queue.fetch { (database: FMDatabase!) -> Void in
//
let fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database) // let fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database)
//
DispatchQueue.main.async() { () -> Void in // DispatchQueue.main.async() { () -> Void in
//
let articles = self.articleCache.uniquedArticles(fetchedArticles, statusesTable: self.statusesTable) // let articles = self.articleCache.uniquedArticles(fetchedArticles, statusesTable: self.statusesTable)
let filteredArticles = self.filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count]) // let filteredArticles = self.filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count])
resultBlock(filteredArticles) // resultBlock(filteredArticles)
} // }
} // }
}
func feedIDCountDictionariesWithResultSet(_ resultSet: FMResultSet) -> [String: Int] {
var counts = [String: Int]()
while (resultSet.next()) {
if let oneFeedID = resultSet.string(forColumnIndex: 0) {
let count = resultSet.int(forColumnIndex: 1)
counts[oneFeedID] = Int(count)
}
}
return counts
}
func countsForAllFeeds(_ database: FMDatabase) -> [String: Int] {
let sql = "select distinct feedID, count(*) as count from articles group by feedID;"
if let resultSet = database.executeQuery(sql, withArgumentsIn: []) {
return feedIDCountDictionariesWithResultSet(resultSet)
}
return [String: Int]()
}
func countsForFeedIDs(_ feedIDs: [String], _ database: FMDatabase) -> [String: Int] {
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let sql = "select distinct feedID, count(*) from articles where feedID in \(placeholders) group by feedID;"
logSQL(sql)
if let resultSet = database.executeQuery(sql, withArgumentsIn: feedIDs) {
return feedIDCountDictionariesWithResultSet(resultSet)
}
return [String: Int]()
} }
func fetchUnreadArticlesForFolder(_ folder: Folder) -> Set<Article> { func fetchUnreadArticlesForFolder(_ folder: Folder) -> Set<Article> {
return fetchUnreadArticlesForFeedIDs(folder.flattenedFeedIDs()) return Set<Article>() // TODO
// return fetchUnreadArticlesForFeedIDs(folder.flattenedFeedIDs())
} }
func fetchUnreadArticlesForFeedIDs(_ feedIDs: [String]) -> Set<Article> {
if feedIDs.isEmpty {
return Set<Article>()
}
var fetchedArticles = Set<Article>()
var counts = [String: Int]()
queue.fetchSync { (database: FMDatabase!) -> Void in
counts = self.countsForFeedIDs(feedIDs, database)
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read = 0
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
let sql = "select * from articles natural join statuses where feedID in \(placeholders) and read=0;"
logSQL(sql)
if let resultSet = database.executeQuery(sql, withArgumentsIn: feedIDs) {
fetchedArticles = self.articlesWithResultSet(resultSet)
}
}
let articles = articleCache.uniquedArticles(fetchedArticles, statusesTable: statusesTable)
return filteredArticles(articles, feedCounts: counts)
}
typealias UnreadCountCompletionBlock = ([String: Int]) -> Void //feedID: unreadCount
func updateUnreadCounts(for feedIDs: Set<String>, completion: @escaping UnreadCountCompletionBlock) {
queue.fetch { (database: FMDatabase!) -> Void in
var unreadCounts = [String: Int]()
for oneFeedID in feedIDs {
unreadCounts[oneFeedID] = self.unreadCount(oneFeedID, database)
}
DispatchQueue.main.async() {
completion(unreadCounts)
}
}
}
// MARK: Updating Articles // MARK: - Unread Counts
typealias UnreadCountTable = [String: Int] // feedID: unreadCount
typealias UnreadCountCompletionBlock = (UnreadCountTable) -> Void //feedID: unreadCount
func fetchUnreadCounts(for feeds: Set<Feed>, completion: @escaping UnreadCountCompletionBlock) {
// let feedIDs = feeds.feedIDs()
//
// queue.fetch { (database: FMDatabase!) -> Void in
//
// var unreadCounts = UnreadCountTable()
// for oneFeedID in feedIDs {
// unreadCounts[oneFeedID] = self.unreadCount(oneFeedID, database)
// }
//
// DispatchQueue.main.async() {
// completion(unreadCounts)
// }
// }
}
// MARK: - Updating Articles
func updateFeedWithParsedFeed(_ feed: Feed, parsedFeed: ParsedFeed, completionHandler: @escaping RSVoidCompletionBlock) { func updateFeedWithParsedFeed(_ feed: Feed, parsedFeed: ParsedFeed, completionHandler: @escaping RSVoidCompletionBlock) {
if parsedFeed.items.isEmpty { // if parsedFeed.items.isEmpty {
completionHandler() // completionHandler()
return // return
} // }
//
let parsedArticlesDictionary = self.articlesDictionary(parsedFeed.items as NSSet) as! [String: ParsedItem] // let parsedArticlesDictionary = self.articlesDictionary(parsedFeed.items as NSSet) as! [String: ParsedItem]
//
fetchArticlesForFeedAsync(feed) { (articles) -> Void in // fetchArticlesForFeedAsync(feed) { (articles) -> Void in
//
let articlesDictionary = self.articlesDictionary(articles as NSSet) as! [String: Article] // let articlesDictionary = self.articlesDictionary(articles as NSSet) as! [String: Article]
self.updateArticles(articlesDictionary, parsedArticles: parsedArticlesDictionary, feed: feed, completionHandler: completionHandler) // self.updateArticles(articlesDictionary, parsedArticles: parsedArticlesDictionary, feed: feed, completionHandler: completionHandler)
} // }
} }
// MARK: Status // MARK: - Status
func markArticles(_ articles: NSSet, statusKey: ArticleStatusKey, flag: Bool) { func markArticles(_ articles: Set<Article>, statusKey: ArticleStatusKey, flag: Bool) {
statusesTable.markArticles(articles as! Set<Article>, statusKey: statusKey, flag: flag) // statusesTable.markArticles(articles, statusKey: statusKey, flag: flag)
} }
} }
// MARK: Private // MARK: - Private
private extension Database { private extension Database {
// func feedIDCountDictionariesWithResultSet(_ resultSet: FMResultSet) -> [String: Int] {
//
// var counts = [String: Int]()
//
// while (resultSet.next()) {
//
// if let oneFeedID = resultSet.string(forColumnIndex: 0) {
// let count = resultSet.int(forColumnIndex: 1)
// counts[oneFeedID] = Int(count)
// }
// }
//
// return counts
// }
// func countsForAllFeeds(_ database: FMDatabase) -> [String: Int] {
//
// let sql = "select distinct feedID, count(*) as count from articles group by feedID;"
//
// if let resultSet = database.executeQuery(sql, withArgumentsIn: []) {
// return feedIDCountDictionariesWithResultSet(resultSet)
// }
//
// return [String: Int]()
// }
// func countsForFeedIDs(_ feedIDs: [String], _ database: FMDatabase) -> [String: Int] {
//
// let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
// let sql = "select distinct feedID, count(*) from articles where feedID in \(placeholders) group by feedID;"
// logSQL(sql)
//
// if let resultSet = database.executeQuery(sql, withArgumentsIn: feedIDs) {
// return feedIDCountDictionariesWithResultSet(resultSet)
// }
//
// return [String: Int]()
//
// }
// func fetchUnreadArticlesForFeedIDs(_ feedIDs: [String]) -> Set<Article> {
//
// if feedIDs.isEmpty {
// return Set<Article>()
// }
//
// var fetchedArticles = Set<Article>()
// var counts = [String: Int]()
//
// queue.fetchSync { (database: FMDatabase!) -> Void in
//
// counts = self.countsForFeedIDs(feedIDs, database)
//
// // select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read = 0
//
// let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))!
// let sql = "select * from articles natural join statuses where feedID in \(placeholders) and read=0;"
// logSQL(sql)
//
// if let resultSet = database.executeQuery(sql, withArgumentsIn: feedIDs) {
// fetchedArticles = self.articlesWithResultSet(resultSet)
// }
// }
//
// let articles = articleCache.uniquedArticles(fetchedArticles, statusesTable: statusesTable)
// return filteredArticles(articles, feedCounts: counts)
// }
// MARK: Saving Articles // MARK: Saving Articles
func saveUpdatedAndNewArticles(_ articleChanges: Set<NSDictionary>, newArticles: Set<Article>) { // func saveUpdatedAndNewArticles(_ articleChanges: Set<NSDictionary>, newArticles: Set<Article>) {
//
if articleChanges.isEmpty && newArticles.isEmpty { // if articleChanges.isEmpty && newArticles.isEmpty {
return // return
} // }
//
statusesTable.assertNoMissingStatuses(newArticles) // statusesTable.assertNoMissingStatuses(newArticles)
articleCache.cacheArticles(newArticles) // articleCache.cacheArticles(newArticles)
//
let newArticleDictionaries = newArticles.map { (oneArticle) in // let newArticleDictionaries = newArticles.map { (oneArticle) in
return oneArticle.databaseDictionary() // return oneArticle.databaseDictionary()
} // }
//
queue.update { (database: FMDatabase!) -> Void in // queue.update { (database: FMDatabase!) -> Void in
//
if !articleChanges.isEmpty { // if !articleChanges.isEmpty {
//
for oneDictionary in articleChanges { // for oneDictionary in articleChanges {
//
let oneArticleDictionary = oneDictionary.mutableCopy() as! NSMutableDictionary // let oneArticleDictionary = oneDictionary.mutableCopy() as! NSMutableDictionary
let articleID = oneArticleDictionary[DatabaseKey.articleID]! // let articleID = oneArticleDictionary[DatabaseKey.articleID]!
oneArticleDictionary.removeObject(forKey: DatabaseKey.articleID) // oneArticleDictionary.removeObject(forKey: DatabaseKey.articleID)
//
let _ = database.rs_updateRows(with: oneArticleDictionary as [NSObject: AnyObject], whereKey: DatabaseKey.articleID, equalsValue: articleID, tableName: DatabaseTableName.articles) // let _ = database.rs_updateRows(with: oneArticleDictionary as [NSObject: AnyObject], whereKey: DatabaseKey.articleID, equalsValue: articleID, tableName: DatabaseTableName.articles)
} // }
//
} // }
if !newArticleDictionaries.isEmpty { // if !newArticleDictionaries.isEmpty {
//
for oneNewArticleDictionary in newArticleDictionaries { // for oneNewArticleDictionary in newArticleDictionaries {
let _ = database.rs_insertRow(with: oneNewArticleDictionary as [NSObject: AnyObject], insertType: RSDatabaseInsertOrReplace, tableName: DatabaseTableName.articles) // let _ = database.rs_insertRow(with: oneNewArticleDictionary as [NSObject: AnyObject], insertType: RSDatabaseInsertOrReplace, tableName: DatabaseTableName.articles)
} // }
} // }
} // }
} // }
//
// MARK: Updating Articles // // MARK: Updating Articles
//
func updateArticles(_ articles: [String: Article], parsedArticles: [String: ParsedItem], feed: Feed, completionHandler: @escaping RSVoidCompletionBlock) { // func updateArticles(_ articles: [String: Article], parsedArticles: [String: ParsedItem], feed: Feed, completionHandler: @escaping RSVoidCompletionBlock) {
//
statusesTable.ensureStatusesForParsedArticles(Set(parsedArticles.values)) { // statusesTable.ensureStatusesForParsedArticles(Set(parsedArticles.values)) {
//
let articleChanges = self.updateExistingArticles(articles, parsedArticles) // let articleChanges = self.updateExistingArticles(articles, parsedArticles)
let newArticles = self.createNewArticles(articles, parsedArticles: parsedArticles, feedID: feed.feedID) // let newArticles = self.createNewArticles(articles, parsedArticles: parsedArticles, feedID: feed.feedID)
//
self.saveUpdatedAndNewArticles(articleChanges, newArticles: newArticles) // self.saveUpdatedAndNewArticles(articleChanges, newArticles: newArticles)
//
completionHandler() // completionHandler()
} // }
} // }
//
func articlesDictionary(_ articles: NSSet) -> [String: AnyObject] { // func articlesDictionary(_ articles: NSSet) -> [String: AnyObject] {
//
var d = [String: AnyObject]() // var d = [String: AnyObject]()
for oneArticle in articles { // for oneArticle in articles {
let oneArticleID = (oneArticle as AnyObject).value(forKey: DatabaseKey.articleID) as! String // let oneArticleID = (oneArticle as AnyObject).value(forKey: DatabaseKey.articleID) as! String
d[oneArticleID] = oneArticle as AnyObject // d[oneArticleID] = oneArticle as AnyObject
} // }
return d // return d
} // }
//
func updateExistingArticles(_ articles: [String: Article], _ parsedArticles: [String: ParsedItem]) -> Set<NSDictionary> { // func updateExistingArticles(_ articles: [String: Article], _ parsedArticles: [String: ParsedItem]) -> Set<NSDictionary> {
//
var articleChanges = Set<NSDictionary>() // var articleChanges = Set<NSDictionary>()
//
for oneArticle in articles.values { // for oneArticle in articles.values {
if let oneParsedArticle = parsedArticles[oneArticle.articleID] { // if let oneParsedArticle = parsedArticles[oneArticle.articleID] {
if let oneArticleChanges = oneArticle.updateWithParsedArticle(oneParsedArticle) { // if let oneArticleChanges = oneArticle.updateWithParsedArticle(oneParsedArticle) {
articleChanges.insert(oneArticleChanges) // articleChanges.insert(oneArticleChanges)
} // }
} // }
} // }
//
return articleChanges // return articleChanges
} // }
//
// MARK: Creating Articles // // MARK: Creating Articles
//
func createNewArticlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> { // func createNewArticlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> {
//
return Set(parsedArticles.map { Article(account: account, feedID: feedID, parsedArticle: $0) }) // return Set(parsedArticles.map { Article(account: account, feedID: feedID, parsedArticle: $0) })
} // }
//
func articlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> { // func articlesWithParsedArticles(_ parsedArticles: Set<ParsedItem>, feedID: String) -> Set<Article> {
//
var localArticles = Set<Article>() // var localArticles = Set<Article>()
//
for oneParsedArticle in parsedArticles { // for oneParsedArticle in parsedArticles {
let oneLocalArticle = Article(account: self.account, feedID: feedID, parsedArticle: oneParsedArticle) // let oneLocalArticle = Article(account: self.account, feedID: feedID, parsedArticle: oneParsedArticle)
localArticles.insert(oneLocalArticle) // localArticles.insert(oneLocalArticle)
} // }
//
return localArticles // return localArticles
} // }
//
func createNewArticles(_ existingArticles: [String: Article], parsedArticles: [String: ParsedItem], feedID: String) -> Set<Article> { // func createNewArticles(_ existingArticles: [String: Article], parsedArticles: [String: ParsedItem], feedID: String) -> Set<Article> {
//
let newParsedArticles = parsedArticlesMinusExistingArticles(parsedArticles, existingArticles: existingArticles) // let newParsedArticles = parsedArticlesMinusExistingArticles(parsedArticles, existingArticles: existingArticles)
let newArticles = createNewArticlesWithParsedArticles(newParsedArticles, feedID: feedID) // let newArticles = createNewArticlesWithParsedArticles(newParsedArticles, feedID: feedID)
//
statusesTable.attachCachedUniqueStatuses(newArticles) // statusesTable.attachCachedUniqueStatuses(newArticles)
//
return newArticles // return newArticles
} // }
//
func parsedArticlesMinusExistingArticles(_ parsedArticles: [String: ParsedItem], existingArticles: [String: Article]) -> Set<ParsedItem> { // func parsedArticlesMinusExistingArticles(_ parsedArticles: [String: ParsedItem], existingArticles: [String: Article]) -> Set<ParsedItem> {
//
var result = Set<ParsedItem>() // var result = Set<ParsedItem>()
//
for oneParsedArticle in parsedArticles.values { // for oneParsedArticle in parsedArticles.values {
//
if let _ = existingArticles[oneParsedArticle.databaseID] { // if let _ = existingArticles[oneParsedArticle.databaseID] {
continue // continue
} // }
result.insert(oneParsedArticle) // result.insert(oneParsedArticle)
} // }
//
return result // return result
} // }
//
// MARK: Fetching Articles // // MARK: Fetching Articles
//
func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]?) -> Set<Article> { // func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]?) -> Set<Article> {
//
let sql = "select * from articles where \(whereClause);" // let sql = "select * from articles where \(whereClause);"
logSQL(sql) // logSQL(sql)
//
if let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) { // if let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) {
return articlesWithResultSet(resultSet, database) // return articlesWithResultSet(resultSet, database)
} // }
//
return Set<Article>() // return Set<Article>()
} // }
//
func articlesWithResultSet(_ resultSet: FMResultSet, _ database: FMDatabase) -> Set<Article> { // func articlesWithResultSet(_ resultSet: FMResultSet, _ database: FMDatabase) -> Set<Article> {
//
let fetchedArticles = resultSet.mapToSet { Article(account: self.account, row: $0) } // let fetchedArticles = resultSet.mapToSet { Article(account: self.account, row: $0) }
//
statusesTable.attachStatuses(fetchedArticles, database) // statusesTable.attachStatuses(fetchedArticles, database)
authorsTable.attachAuthors(fetchedArticles, database) // authorsTable.attachAuthors(fetchedArticles, database)
tagsTable.attachTags(fetchedArticles, database) // tagsTable.attachTags(fetchedArticles, database)
attachmentsTable.attachAttachments(fetchedArticles, database) // attachmentsTable.attachAttachments(fetchedArticles, database)
//
return fetchedArticles // return fetchedArticles
} // }
//
func fetchArticlesForFeedID(_ feedID: String, database: FMDatabase) -> Set<Article> { // func fetchArticlesForFeedID(_ feedID: String, database: FMDatabase) -> Set<Article> {
//
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject]) // return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject])
} // }
//
// MARK: Unread counts // // MARK: Unread counts
//
func numberOfArticles(_ feedID: String, _ database: FMDatabase) -> Int { // func numberOfArticles(_ feedID: String, _ database: FMDatabase) -> Int {
//
let sql = "select count(*) from articles where feedID = ?;" // let sql = "select count(*) from articles where feedID = ?;"
logSQL(sql) // logSQL(sql)
//
return numberWithSQLAndParameters(sql, parameters: [feedID], database) // return numberWithSQLAndParameters(sql, parameters: [feedID], database)
} // }
//
func unreadCount(_ feedID: String, _ database: FMDatabase) -> Int { // func unreadCount(_ feedID: String, _ database: FMDatabase) -> Int {
//
let totalNumberOfArticles = numberOfArticles(feedID, database) // let totalNumberOfArticles = numberOfArticles(feedID, database)
//
if totalNumberOfArticles <= minimumNumberOfArticles { // if totalNumberOfArticles <= minimumNumberOfArticles {
return unreadCountIgnoringCutoffDate(feedID, database) // return unreadCountIgnoringCutoffDate(feedID, database)
} // }
return unreadCountRespectingCutoffDate(feedID, database) // return unreadCountRespectingCutoffDate(feedID, database)
} // }
//
func unreadCountIgnoringCutoffDate(_ feedID: String, _ database: FMDatabase) -> Int { // func unreadCountIgnoringCutoffDate(_ feedID: String, _ database: FMDatabase) -> Int {
//
let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0;" // let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0;"
logSQL(sql) // logSQL(sql)
//
return numberWithSQLAndParameters(sql, parameters: [feedID], database) // return numberWithSQLAndParameters(sql, parameters: [feedID], database)
} // }
//
func unreadCountRespectingCutoffDate(_ feedID: String, _ database: FMDatabase) -> Int { // func unreadCountRespectingCutoffDate(_ feedID: String, _ database: FMDatabase) -> Int {
//
let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0 and (starred=1 or dateArrived>?);" // let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0 and (starred=1 or dateArrived>?);"
logSQL(sql) // logSQL(sql)
//
return numberWithSQLAndParameters(sql, parameters: [feedID, articleArrivalCutoffDate], database) // return numberWithSQLAndParameters(sql, parameters: [feedID, articleArrivalCutoffDate], database)
} // }
//
// MARK: Filtering out old articles // // MARK: Filtering out old articles
//
func articleIsOlderThanCutoffDate(_ article: Article) -> Bool { // func articleIsOlderThanCutoffDate(_ article: Article) -> Bool {
//
if let dateArrived = article.status?.dateArrived { // if let dateArrived = article.status?.dateArrived {
return dateArrived < articleArrivalCutoffDate // return dateArrived < articleArrivalCutoffDate
} // }
return false // return false
} // }
//
func articleShouldBeSavedForever(_ article: Article) -> Bool { // func articleShouldBeSavedForever(_ article: Article) -> Bool {
//
return article.status.starred // return article.status.starred
} // }
//
func articleShouldAppearToUser(_ article: Article, _ numberOfArticlesInFeed: Int) -> Bool { // func articleShouldAppearToUser(_ article: Article, _ numberOfArticlesInFeed: Int) -> Bool {
//
if numberOfArticlesInFeed <= minimumNumberOfArticles { // if numberOfArticlesInFeed <= minimumNumberOfArticles {
return true // return true
} // }
return articleShouldBeSavedForever(article) || !articleIsOlderThanCutoffDate(article) // return articleShouldBeSavedForever(article) || !articleIsOlderThanCutoffDate(article)
} // }
//
private static let minimumNumberOfArticlesInFeed = 10 // private static let minimumNumberOfArticlesInFeed = 10
//
func filteredArticles(_ articles: Set<Article>, feedCounts: [String: Int]) -> Set<Article> { // func filteredArticles(_ articles: Set<Article>, feedCounts: [String: Int]) -> Set<Article> {
//
var articlesSet = Set<Article>() // var articlesSet = Set<Article>()
//
for oneArticle in articles { // for oneArticle in articles {
if let feedCount = feedCounts[oneArticle.feedID], articleShouldAppearToUser(oneArticle, feedCount) { // if let feedCount = feedCounts[oneArticle.feedID], articleShouldAppearToUser(oneArticle, feedCount) {
articlesSet.insert(oneArticle) // articlesSet.insert(oneArticle)
} // }
//
} // }
//
return articlesSet // return articlesSet
} // }
//
//
func feedIDsFromArticles(_ articles: Set<Article>) -> Set<String> { // func feedIDsFromArticles(_ articles: Set<Article>) -> Set<String> {
//
return Set(articles.map { $0.feedID }) // return Set(articles.map { $0.feedID })
} // }
//
func deletePossibleOldArticles(_ articles: Set<Article>) { // func deletePossibleOldArticles(_ articles: Set<Article>) {
//
let feedIDs = feedIDsFromArticles(articles) // let feedIDs = feedIDsFromArticles(articles)
if feedIDs.isEmpty { // if feedIDs.isEmpty {
return // return
} // }
} // }
} }

View File

@ -18,6 +18,7 @@
8455807A1F0AF67D003CCFA1 /* ArticleStatus+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */; }; 8455807A1F0AF67D003CCFA1 /* ArticleStatus+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */; };
8455807C1F0C0DBD003CCFA1 /* Attachment+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8455807B1F0C0DBD003CCFA1 /* Attachment+Database.swift */; }; 8455807C1F0C0DBD003CCFA1 /* Attachment+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8455807B1F0C0DBD003CCFA1 /* Attachment+Database.swift */; };
846146271F0ABC7B00870CB3 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846146241F0ABC7400870CB3 /* RSParser.framework */; }; 846146271F0ABC7B00870CB3 /* RSParser.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 846146241F0ABC7400870CB3 /* RSParser.framework */; };
846FB36B1F4A937B00EAB81D /* Feed+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 846FB36A1F4A937B00EAB81D /* Feed+Database.swift */; };
84BB4BA21F119C5400858766 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B981F119C4900858766 /* RSCore.framework */; }; 84BB4BA21F119C5400858766 /* RSCore.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 84BB4B981F119C4900858766 /* RSCore.framework */; };
84BB4BA91F11A32800858766 /* TagsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BB4BA81F11A32800858766 /* TagsTable.swift */; }; 84BB4BA91F11A32800858766 /* TagsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BB4BA81F11A32800858766 /* TagsTable.swift */; };
84D0DEA11F4A429800073503 /* String+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D0DEA01F4A429800073503 /* String+Database.swift */; }; 84D0DEA11F4A429800073503 /* String+Database.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84D0DEA01F4A429800073503 /* String+Database.swift */; };
@ -123,6 +124,7 @@
845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "ArticleStatus+Database.swift"; path = "Extensions/ArticleStatus+Database.swift"; sourceTree = "<group>"; }; 845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "ArticleStatus+Database.swift"; path = "Extensions/ArticleStatus+Database.swift"; sourceTree = "<group>"; };
8455807B1F0C0DBD003CCFA1 /* Attachment+Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Attachment+Database.swift"; path = "Extensions/Attachment+Database.swift"; sourceTree = "<group>"; }; 8455807B1F0C0DBD003CCFA1 /* Attachment+Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Attachment+Database.swift"; path = "Extensions/Attachment+Database.swift"; sourceTree = "<group>"; };
8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSParser.xcodeproj; path = ../RSParser/RSParser.xcodeproj; sourceTree = "<group>"; }; 8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSParser.xcodeproj; path = ../RSParser/RSParser.xcodeproj; sourceTree = "<group>"; };
846FB36A1F4A937B00EAB81D /* Feed+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Feed+Database.swift"; path = "Extensions/Feed+Database.swift"; sourceTree = "<group>"; };
84BB4B8F1F119C4900858766 /* RSCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSCore.xcodeproj; path = ../RSCore/RSCore.xcodeproj; sourceTree = "<group>"; }; 84BB4B8F1F119C4900858766 /* RSCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSCore.xcodeproj; path = ../RSCore/RSCore.xcodeproj; sourceTree = "<group>"; };
84BB4BA81F11A32800858766 /* TagsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsTable.swift; sourceTree = "<group>"; }; 84BB4BA81F11A32800858766 /* TagsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsTable.swift; sourceTree = "<group>"; };
84D0DEA01F4A429800073503 /* String+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "String+Database.swift"; path = "Extensions/String+Database.swift"; sourceTree = "<group>"; }; 84D0DEA01F4A429800073503 /* String+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "String+Database.swift"; path = "Extensions/String+Database.swift"; sourceTree = "<group>"; };
@ -210,6 +212,7 @@
isa = PBXGroup; isa = PBXGroup;
children = ( children = (
845580771F0AF678003CCFA1 /* Folder+Database.swift */, 845580771F0AF678003CCFA1 /* Folder+Database.swift */,
846FB36A1F4A937B00EAB81D /* Feed+Database.swift */,
845580751F0AF670003CCFA1 /* Article+Database.swift */, 845580751F0AF670003CCFA1 /* Article+Database.swift */,
845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */, 845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */,
84F20F901F1810DD00D8E682 /* Author+Database.swift */, 84F20F901F1810DD00D8E682 /* Author+Database.swift */,
@ -472,6 +475,7 @@
845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */, 845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */,
840405CF1F1A963700DF0296 /* AttachmentsTable.swift in Sources */, 840405CF1F1A963700DF0296 /* AttachmentsTable.swift in Sources */,
84D0DEA11F4A429800073503 /* String+Database.swift in Sources */, 84D0DEA11F4A429800073503 /* String+Database.swift in Sources */,
846FB36B1F4A937B00EAB81D /* Feed+Database.swift in Sources */,
843CB9961F34174100EE6581 /* Author+Database.swift in Sources */, 843CB9961F34174100EE6581 /* Author+Database.swift in Sources */,
845580781F0AF678003CCFA1 /* Folder+Database.swift in Sources */, 845580781F0AF678003CCFA1 /* Folder+Database.swift in Sources */,
845580761F0AF670003CCFA1 /* Article+Database.swift in Sources */, 845580761F0AF670003CCFA1 /* Article+Database.swift in Sources */,

View File

@ -0,0 +1,18 @@
//
// Feed+Database.swift
// Database
//
// Created by Brent Simmons on 8/20/17.
// Copyright © 2017 Ranchero Software. All rights reserved.
//
import Foundation
import Data
extension Set where Element == Feed {
func feedIDs() -> Set<String> {
return Set(map { $0.feedID })
}
}

Binary file not shown.