diff --git a/Frameworks/Data/Article.swift b/Frameworks/Data/Article.swift index d8ae01422..4f3b0a25d 100644 --- a/Frameworks/Data/Article.swift +++ b/Frameworks/Data/Article.swift @@ -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?, 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?, attachments: [Attachment]?, accountInfo: AccountInfo?) { self.account = account self.feedID = feedID @@ -59,16 +59,14 @@ public final class Article: Hashable { self.attachments = attachments self.accountInfo = accountInfo - let _databaseID: String - if let databaseID = databaseID { - _databaseID = databaseID + if let articleID = articleID { + self.articleID = articleID } 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 { diff --git a/Frameworks/Data/Author.swift b/Frameworks/Data/Author.swift index 658a09922..67fb137a3 100644 --- a/Frameworks/Data/Author.swift +++ b/Frameworks/Data/Author.swift @@ -44,6 +44,6 @@ public struct Author: Hashable { 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 } } diff --git a/Frameworks/Database/Constants.swift b/Frameworks/Database/Constants.swift index 22fa5b7e5..a85a78b4a 100644 --- a/Frameworks/Database/Constants.swift +++ b/Frameworks/Database/Constants.swift @@ -69,4 +69,5 @@ public struct RelationshipName { static let authors = "authors" static let tags = "tags" + static let attachments = "attachments" } diff --git a/Frameworks/Database/Database.swift b/Frameworks/Database/Database.swift index fb77c17dd..a2fbf20c1 100644 --- a/Frameworks/Database/Database.swift +++ b/Frameworks/Database/Database.swift @@ -51,7 +51,7 @@ final class Database { self.tagsLookupTable = DatabaseLookupTable(name: DatabaseTableName.tags, objectIDKey: DatabaseKey.articleID, relatedObjectIDKey: DatabaseKey.tagName, relatedTable: tagsTable, relationshipName: RelationshipName.tags) 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 createStatements = try! NSString(contentsOfFile: createStatementsPath, encoding: String.Encoding.utf8.rawValue) @@ -59,399 +59,407 @@ final class Database { queue.vacuumIfNeeded() } - // MARK: Fetching Articles + // MARK: - Fetching Articles func fetchArticlesForFeed(_ feed: Feed) -> Set
{ - var fetchedArticles = Set
() - let feedID = feed.feedID - - queue.fetchSync { (database: FMDatabase!) -> Void in - - fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database) - } - - let articles = articleCache.uniquedArticles(fetchedArticles, statusesTable: statusesTable) - return filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count]) + return Set
() // TODO +// var fetchedArticles = Set
() +// let feedID = feed.feedID +// +// queue.fetchSync { (database: FMDatabase!) -> Void in +// +// fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database) +// } +// +// let articles = articleCache.uniquedArticles(fetchedArticles, statusesTable: statusesTable) +// return filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count]) } func fetchArticlesForFeedAsync(_ feed: Feed, _ resultBlock: @escaping ArticleResultBlock) { - let feedID = feed.feedID - - queue.fetch { (database: FMDatabase!) -> Void in - - let fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database) - - DispatchQueue.main.async() { () -> Void in - - let articles = self.articleCache.uniquedArticles(fetchedArticles, statusesTable: self.statusesTable) - let filteredArticles = self.filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count]) - 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]() - +// let feedID = feed.feedID +// +// queue.fetch { (database: FMDatabase!) -> Void in +// +// let fetchedArticles = self.fetchArticlesForFeedID(feedID, database: database) +// +// DispatchQueue.main.async() { () -> Void in +// +// let articles = self.articleCache.uniquedArticles(fetchedArticles, statusesTable: self.statusesTable) +// let filteredArticles = self.filteredArticles(articles, feedCounts: [feed.feedID: fetchedArticles.count]) +// resultBlock(filteredArticles) +// } +// } } func fetchUnreadArticlesForFolder(_ folder: Folder) -> Set
{ - return fetchUnreadArticlesForFeedIDs(folder.flattenedFeedIDs()) + return Set
() // TODO +// return fetchUnreadArticlesForFeedIDs(folder.flattenedFeedIDs()) } - - func fetchUnreadArticlesForFeedIDs(_ feedIDs: [String]) -> Set
{ - - if feedIDs.isEmpty { - return Set
() - } - - var fetchedArticles = Set
() - 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, 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, 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) { - if parsedFeed.items.isEmpty { - completionHandler() - return - } - - let parsedArticlesDictionary = self.articlesDictionary(parsedFeed.items as NSSet) as! [String: ParsedItem] - - fetchArticlesForFeedAsync(feed) { (articles) -> Void in - - let articlesDictionary = self.articlesDictionary(articles as NSSet) as! [String: Article] - self.updateArticles(articlesDictionary, parsedArticles: parsedArticlesDictionary, feed: feed, completionHandler: completionHandler) - } +// if parsedFeed.items.isEmpty { +// completionHandler() +// return +// } +// +// let parsedArticlesDictionary = self.articlesDictionary(parsedFeed.items as NSSet) as! [String: ParsedItem] +// +// fetchArticlesForFeedAsync(feed) { (articles) -> Void in +// +// let articlesDictionary = self.articlesDictionary(articles as NSSet) as! [String: Article] +// self.updateArticles(articlesDictionary, parsedArticles: parsedArticlesDictionary, feed: feed, completionHandler: completionHandler) +// } } - // MARK: Status + // MARK: - Status - func markArticles(_ articles: NSSet, statusKey: ArticleStatusKey, flag: Bool) { + func markArticles(_ articles: Set
, statusKey: ArticleStatusKey, flag: Bool) { - statusesTable.markArticles(articles as! Set
, statusKey: statusKey, flag: flag) +// statusesTable.markArticles(articles, statusKey: statusKey, flag: flag) } } -// MARK: Private +// MARK: - Private 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
{ + // + // if feedIDs.isEmpty { + // return Set
() + // } + // + // var fetchedArticles = Set
() + // 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 - func saveUpdatedAndNewArticles(_ articleChanges: Set, newArticles: Set
) { - - if articleChanges.isEmpty && newArticles.isEmpty { - return - } - - statusesTable.assertNoMissingStatuses(newArticles) - articleCache.cacheArticles(newArticles) - - let newArticleDictionaries = newArticles.map { (oneArticle) in - return oneArticle.databaseDictionary() - } - - queue.update { (database: FMDatabase!) -> Void in - - if !articleChanges.isEmpty { - - for oneDictionary in articleChanges { - - let oneArticleDictionary = oneDictionary.mutableCopy() as! NSMutableDictionary - let articleID = oneArticleDictionary[DatabaseKey.articleID]! - oneArticleDictionary.removeObject(forKey: DatabaseKey.articleID) - - let _ = database.rs_updateRows(with: oneArticleDictionary as [NSObject: AnyObject], whereKey: DatabaseKey.articleID, equalsValue: articleID, tableName: DatabaseTableName.articles) - } - - } - if !newArticleDictionaries.isEmpty { - - for oneNewArticleDictionary in newArticleDictionaries { - let _ = database.rs_insertRow(with: oneNewArticleDictionary as [NSObject: AnyObject], insertType: RSDatabaseInsertOrReplace, tableName: DatabaseTableName.articles) - } - } - } - } - - // MARK: Updating Articles - - func updateArticles(_ articles: [String: Article], parsedArticles: [String: ParsedItem], feed: Feed, completionHandler: @escaping RSVoidCompletionBlock) { - - statusesTable.ensureStatusesForParsedArticles(Set(parsedArticles.values)) { - - let articleChanges = self.updateExistingArticles(articles, parsedArticles) - let newArticles = self.createNewArticles(articles, parsedArticles: parsedArticles, feedID: feed.feedID) - - self.saveUpdatedAndNewArticles(articleChanges, newArticles: newArticles) - - completionHandler() - } - } - - func articlesDictionary(_ articles: NSSet) -> [String: AnyObject] { - - var d = [String: AnyObject]() - for oneArticle in articles { - let oneArticleID = (oneArticle as AnyObject).value(forKey: DatabaseKey.articleID) as! String - d[oneArticleID] = oneArticle as AnyObject - } - return d - } - - func updateExistingArticles(_ articles: [String: Article], _ parsedArticles: [String: ParsedItem]) -> Set { - - var articleChanges = Set() - - for oneArticle in articles.values { - if let oneParsedArticle = parsedArticles[oneArticle.articleID] { - if let oneArticleChanges = oneArticle.updateWithParsedArticle(oneParsedArticle) { - articleChanges.insert(oneArticleChanges) - } - } - } - - return articleChanges - } - - // MARK: Creating Articles - - func createNewArticlesWithParsedArticles(_ parsedArticles: Set, feedID: String) -> Set
{ - - return Set(parsedArticles.map { Article(account: account, feedID: feedID, parsedArticle: $0) }) - } - - func articlesWithParsedArticles(_ parsedArticles: Set, feedID: String) -> Set
{ - - var localArticles = Set
() - - for oneParsedArticle in parsedArticles { - let oneLocalArticle = Article(account: self.account, feedID: feedID, parsedArticle: oneParsedArticle) - localArticles.insert(oneLocalArticle) - } - - return localArticles - } - - func createNewArticles(_ existingArticles: [String: Article], parsedArticles: [String: ParsedItem], feedID: String) -> Set
{ - - let newParsedArticles = parsedArticlesMinusExistingArticles(parsedArticles, existingArticles: existingArticles) - let newArticles = createNewArticlesWithParsedArticles(newParsedArticles, feedID: feedID) - - statusesTable.attachCachedUniqueStatuses(newArticles) - - return newArticles - } - - func parsedArticlesMinusExistingArticles(_ parsedArticles: [String: ParsedItem], existingArticles: [String: Article]) -> Set { - - var result = Set() - - for oneParsedArticle in parsedArticles.values { - - if let _ = existingArticles[oneParsedArticle.databaseID] { - continue - } - result.insert(oneParsedArticle) - } - - return result - } - - // MARK: Fetching Articles - - func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]?) -> Set
{ - - let sql = "select * from articles where \(whereClause);" - logSQL(sql) - - if let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) { - return articlesWithResultSet(resultSet, database) - } - - return Set
() - } - - func articlesWithResultSet(_ resultSet: FMResultSet, _ database: FMDatabase) -> Set
{ - - let fetchedArticles = resultSet.mapToSet { Article(account: self.account, row: $0) } - - statusesTable.attachStatuses(fetchedArticles, database) - authorsTable.attachAuthors(fetchedArticles, database) - tagsTable.attachTags(fetchedArticles, database) - attachmentsTable.attachAttachments(fetchedArticles, database) - - return fetchedArticles - } - - func fetchArticlesForFeedID(_ feedID: String, database: FMDatabase) -> Set
{ - - return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject]) - } - - // MARK: Unread counts - - func numberOfArticles(_ feedID: String, _ database: FMDatabase) -> Int { - - let sql = "select count(*) from articles where feedID = ?;" - logSQL(sql) - - return numberWithSQLAndParameters(sql, parameters: [feedID], database) - } - - func unreadCount(_ feedID: String, _ database: FMDatabase) -> Int { - - let totalNumberOfArticles = numberOfArticles(feedID, database) - - if totalNumberOfArticles <= minimumNumberOfArticles { - return unreadCountIgnoringCutoffDate(feedID, database) - } - return unreadCountRespectingCutoffDate(feedID, database) - } - - func unreadCountIgnoringCutoffDate(_ feedID: String, _ database: FMDatabase) -> Int { - - let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0;" - logSQL(sql) - - return numberWithSQLAndParameters(sql, parameters: [feedID], database) - } - - 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>?);" - logSQL(sql) - - return numberWithSQLAndParameters(sql, parameters: [feedID, articleArrivalCutoffDate], database) - } - - // MARK: Filtering out old articles - - func articleIsOlderThanCutoffDate(_ article: Article) -> Bool { - - if let dateArrived = article.status?.dateArrived { - return dateArrived < articleArrivalCutoffDate - } - return false - } - - func articleShouldBeSavedForever(_ article: Article) -> Bool { - - return article.status.starred - } - - func articleShouldAppearToUser(_ article: Article, _ numberOfArticlesInFeed: Int) -> Bool { - - if numberOfArticlesInFeed <= minimumNumberOfArticles { - return true - } - return articleShouldBeSavedForever(article) || !articleIsOlderThanCutoffDate(article) - } - - private static let minimumNumberOfArticlesInFeed = 10 - - func filteredArticles(_ articles: Set
, feedCounts: [String: Int]) -> Set
{ - - var articlesSet = Set
() - - for oneArticle in articles { - if let feedCount = feedCounts[oneArticle.feedID], articleShouldAppearToUser(oneArticle, feedCount) { - articlesSet.insert(oneArticle) - } - - } - - return articlesSet - } - - - func feedIDsFromArticles(_ articles: Set
) -> Set { - - return Set(articles.map { $0.feedID }) - } - - func deletePossibleOldArticles(_ articles: Set
) { - - let feedIDs = feedIDsFromArticles(articles) - if feedIDs.isEmpty { - return - } - } +// func saveUpdatedAndNewArticles(_ articleChanges: Set, newArticles: Set
) { +// +// if articleChanges.isEmpty && newArticles.isEmpty { +// return +// } +// +// statusesTable.assertNoMissingStatuses(newArticles) +// articleCache.cacheArticles(newArticles) +// +// let newArticleDictionaries = newArticles.map { (oneArticle) in +// return oneArticle.databaseDictionary() +// } +// +// queue.update { (database: FMDatabase!) -> Void in +// +// if !articleChanges.isEmpty { +// +// for oneDictionary in articleChanges { +// +// let oneArticleDictionary = oneDictionary.mutableCopy() as! NSMutableDictionary +// let articleID = oneArticleDictionary[DatabaseKey.articleID]! +// oneArticleDictionary.removeObject(forKey: DatabaseKey.articleID) +// +// let _ = database.rs_updateRows(with: oneArticleDictionary as [NSObject: AnyObject], whereKey: DatabaseKey.articleID, equalsValue: articleID, tableName: DatabaseTableName.articles) +// } +// +// } +// if !newArticleDictionaries.isEmpty { +// +// for oneNewArticleDictionary in newArticleDictionaries { +// let _ = database.rs_insertRow(with: oneNewArticleDictionary as [NSObject: AnyObject], insertType: RSDatabaseInsertOrReplace, tableName: DatabaseTableName.articles) +// } +// } +// } +// } +// +// // MARK: Updating Articles +// +// func updateArticles(_ articles: [String: Article], parsedArticles: [String: ParsedItem], feed: Feed, completionHandler: @escaping RSVoidCompletionBlock) { +// +// statusesTable.ensureStatusesForParsedArticles(Set(parsedArticles.values)) { +// +// let articleChanges = self.updateExistingArticles(articles, parsedArticles) +// let newArticles = self.createNewArticles(articles, parsedArticles: parsedArticles, feedID: feed.feedID) +// +// self.saveUpdatedAndNewArticles(articleChanges, newArticles: newArticles) +// +// completionHandler() +// } +// } +// +// func articlesDictionary(_ articles: NSSet) -> [String: AnyObject] { +// +// var d = [String: AnyObject]() +// for oneArticle in articles { +// let oneArticleID = (oneArticle as AnyObject).value(forKey: DatabaseKey.articleID) as! String +// d[oneArticleID] = oneArticle as AnyObject +// } +// return d +// } +// +// func updateExistingArticles(_ articles: [String: Article], _ parsedArticles: [String: ParsedItem]) -> Set { +// +// var articleChanges = Set() +// +// for oneArticle in articles.values { +// if let oneParsedArticle = parsedArticles[oneArticle.articleID] { +// if let oneArticleChanges = oneArticle.updateWithParsedArticle(oneParsedArticle) { +// articleChanges.insert(oneArticleChanges) +// } +// } +// } +// +// return articleChanges +// } +// +// // MARK: Creating Articles +// +// func createNewArticlesWithParsedArticles(_ parsedArticles: Set, feedID: String) -> Set
{ +// +// return Set(parsedArticles.map { Article(account: account, feedID: feedID, parsedArticle: $0) }) +// } +// +// func articlesWithParsedArticles(_ parsedArticles: Set, feedID: String) -> Set
{ +// +// var localArticles = Set
() +// +// for oneParsedArticle in parsedArticles { +// let oneLocalArticle = Article(account: self.account, feedID: feedID, parsedArticle: oneParsedArticle) +// localArticles.insert(oneLocalArticle) +// } +// +// return localArticles +// } +// +// func createNewArticles(_ existingArticles: [String: Article], parsedArticles: [String: ParsedItem], feedID: String) -> Set
{ +// +// let newParsedArticles = parsedArticlesMinusExistingArticles(parsedArticles, existingArticles: existingArticles) +// let newArticles = createNewArticlesWithParsedArticles(newParsedArticles, feedID: feedID) +// +// statusesTable.attachCachedUniqueStatuses(newArticles) +// +// return newArticles +// } +// +// func parsedArticlesMinusExistingArticles(_ parsedArticles: [String: ParsedItem], existingArticles: [String: Article]) -> Set { +// +// var result = Set() +// +// for oneParsedArticle in parsedArticles.values { +// +// if let _ = existingArticles[oneParsedArticle.databaseID] { +// continue +// } +// result.insert(oneParsedArticle) +// } +// +// return result +// } +// +// // MARK: Fetching Articles +// +// func fetchArticlesWithWhereClause(_ database: FMDatabase, whereClause: String, parameters: [AnyObject]?) -> Set
{ +// +// let sql = "select * from articles where \(whereClause);" +// logSQL(sql) +// +// if let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) { +// return articlesWithResultSet(resultSet, database) +// } +// +// return Set
() +// } +// +// func articlesWithResultSet(_ resultSet: FMResultSet, _ database: FMDatabase) -> Set
{ +// +// let fetchedArticles = resultSet.mapToSet { Article(account: self.account, row: $0) } +// +// statusesTable.attachStatuses(fetchedArticles, database) +// authorsTable.attachAuthors(fetchedArticles, database) +// tagsTable.attachTags(fetchedArticles, database) +// attachmentsTable.attachAttachments(fetchedArticles, database) +// +// return fetchedArticles +// } +// +// func fetchArticlesForFeedID(_ feedID: String, database: FMDatabase) -> Set
{ +// +// return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [feedID as AnyObject]) +// } +// +// // MARK: Unread counts +// +// func numberOfArticles(_ feedID: String, _ database: FMDatabase) -> Int { +// +// let sql = "select count(*) from articles where feedID = ?;" +// logSQL(sql) +// +// return numberWithSQLAndParameters(sql, parameters: [feedID], database) +// } +// +// func unreadCount(_ feedID: String, _ database: FMDatabase) -> Int { +// +// let totalNumberOfArticles = numberOfArticles(feedID, database) +// +// if totalNumberOfArticles <= minimumNumberOfArticles { +// return unreadCountIgnoringCutoffDate(feedID, database) +// } +// return unreadCountRespectingCutoffDate(feedID, database) +// } +// +// func unreadCountIgnoringCutoffDate(_ feedID: String, _ database: FMDatabase) -> Int { +// +// let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0;" +// logSQL(sql) +// +// return numberWithSQLAndParameters(sql, parameters: [feedID], database) +// } +// +// 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>?);" +// logSQL(sql) +// +// return numberWithSQLAndParameters(sql, parameters: [feedID, articleArrivalCutoffDate], database) +// } +// +// // MARK: Filtering out old articles +// +// func articleIsOlderThanCutoffDate(_ article: Article) -> Bool { +// +// if let dateArrived = article.status?.dateArrived { +// return dateArrived < articleArrivalCutoffDate +// } +// return false +// } +// +// func articleShouldBeSavedForever(_ article: Article) -> Bool { +// +// return article.status.starred +// } +// +// func articleShouldAppearToUser(_ article: Article, _ numberOfArticlesInFeed: Int) -> Bool { +// +// if numberOfArticlesInFeed <= minimumNumberOfArticles { +// return true +// } +// return articleShouldBeSavedForever(article) || !articleIsOlderThanCutoffDate(article) +// } +// +// private static let minimumNumberOfArticlesInFeed = 10 +// +// func filteredArticles(_ articles: Set
, feedCounts: [String: Int]) -> Set
{ +// +// var articlesSet = Set
() +// +// for oneArticle in articles { +// if let feedCount = feedCounts[oneArticle.feedID], articleShouldAppearToUser(oneArticle, feedCount) { +// articlesSet.insert(oneArticle) +// } +// +// } +// +// return articlesSet +// } +// +// +// func feedIDsFromArticles(_ articles: Set
) -> Set { +// +// return Set(articles.map { $0.feedID }) +// } +// +// func deletePossibleOldArticles(_ articles: Set
) { +// +// let feedIDs = feedIDsFromArticles(articles) +// if feedIDs.isEmpty { +// return +// } +// } } diff --git a/Frameworks/Database/Database.xcodeproj/project.pbxproj b/Frameworks/Database/Database.xcodeproj/project.pbxproj index c583966b9..c2daa7101 100644 --- a/Frameworks/Database/Database.xcodeproj/project.pbxproj +++ b/Frameworks/Database/Database.xcodeproj/project.pbxproj @@ -18,6 +18,7 @@ 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 */; }; 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 */; }; 84BB4BA91F11A32800858766 /* TagsTable.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84BB4BA81F11A32800858766 /* TagsTable.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 = ""; }; 8455807B1F0C0DBD003CCFA1 /* Attachment+Database.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = "Attachment+Database.swift"; path = "Extensions/Attachment+Database.swift"; sourceTree = ""; }; 8461461E1F0ABC7300870CB3 /* RSParser.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSParser.xcodeproj; path = ../RSParser/RSParser.xcodeproj; sourceTree = ""; }; + 846FB36A1F4A937B00EAB81D /* Feed+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "Feed+Database.swift"; path = "Extensions/Feed+Database.swift"; sourceTree = ""; }; 84BB4B8F1F119C4900858766 /* RSCore.xcodeproj */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.pb-project"; name = RSCore.xcodeproj; path = ../RSCore/RSCore.xcodeproj; sourceTree = ""; }; 84BB4BA81F11A32800858766 /* TagsTable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TagsTable.swift; sourceTree = ""; }; 84D0DEA01F4A429800073503 /* String+Database.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = "String+Database.swift"; path = "Extensions/String+Database.swift"; sourceTree = ""; }; @@ -210,6 +212,7 @@ isa = PBXGroup; children = ( 845580771F0AF678003CCFA1 /* Folder+Database.swift */, + 846FB36A1F4A937B00EAB81D /* Feed+Database.swift */, 845580751F0AF670003CCFA1 /* Article+Database.swift */, 845580791F0AF67D003CCFA1 /* ArticleStatus+Database.swift */, 84F20F901F1810DD00D8E682 /* Author+Database.swift */, @@ -472,6 +475,7 @@ 845580671F0AEBCD003CCFA1 /* Constants.swift in Sources */, 840405CF1F1A963700DF0296 /* AttachmentsTable.swift in Sources */, 84D0DEA11F4A429800073503 /* String+Database.swift in Sources */, + 846FB36B1F4A937B00EAB81D /* Feed+Database.swift in Sources */, 843CB9961F34174100EE6581 /* Author+Database.swift in Sources */, 845580781F0AF678003CCFA1 /* Folder+Database.swift in Sources */, 845580761F0AF670003CCFA1 /* Article+Database.swift in Sources */, diff --git a/Frameworks/Database/Extensions/Feed+Database.swift b/Frameworks/Database/Extensions/Feed+Database.swift new file mode 100644 index 000000000..baf961f3b --- /dev/null +++ b/Frameworks/Database/Extensions/Feed+Database.swift @@ -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 { + + return Set(map { $0.feedID }) + } +} diff --git a/ToDo.ooutline b/ToDo.ooutline index fab1f61b1..0c83d3e5a 100644 Binary files a/ToDo.ooutline and b/ToDo.ooutline differ