Add an optional limit parameter to the smart feeds. Fixes

This commit is contained in:
Maurice Parker 2021-03-24 05:43:07 -05:00
parent 7e791a2e7d
commit 9c761c80df
6 changed files with 76 additions and 67 deletions
Account/Sources/Account
ArticlesDatabase/Sources/ArticlesDatabase
Shared/SmartFeeds

@ -53,9 +53,9 @@ public enum AccountType: Int, Codable {
} }
public enum FetchType { public enum FetchType {
case starred case starred(Int?)
case unread case unread(Int?)
case today case today(Int?)
case folder(Folder, Bool) case folder(Folder, Bool)
case webFeed(WebFeed) case webFeed(WebFeed)
case articleIDs(Set<String>) case articleIDs(Set<String>)
@ -674,12 +674,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public func fetchArticles(_ fetchType: FetchType) throws -> Set<Article> { public func fetchArticles(_ fetchType: FetchType) throws -> Set<Article> {
switch fetchType { switch fetchType {
case .starred: case .starred(let limit):
return try fetchStarredArticles() return try fetchStarredArticles(limit: limit)
case .unread: case .unread(let limit):
return try fetchUnreadArticles() return try fetchUnreadArticles(limit: limit)
case .today: case .today(let limit):
return try fetchTodayArticles() return try fetchTodayArticles(limit: limit)
case .folder(let folder, let readFilter): case .folder(let folder, let readFilter):
if readFilter { if readFilter {
return try fetchUnreadArticles(folder: folder) return try fetchUnreadArticles(folder: folder)
@ -699,12 +699,12 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public func fetchArticlesAsync(_ fetchType: FetchType, _ completion: @escaping ArticleSetResultBlock) { public func fetchArticlesAsync(_ fetchType: FetchType, _ completion: @escaping ArticleSetResultBlock) {
switch fetchType { switch fetchType {
case .starred: case .starred(let limit):
fetchStarredArticlesAsync(completion) fetchStarredArticlesAsync(limit: limit, completion)
case .unread: case .unread(let limit):
fetchUnreadArticlesAsync(completion) fetchUnreadArticlesAsync(limit: limit, completion)
case .today: case .today(let limit):
fetchTodayArticlesAsync(completion) fetchTodayArticlesAsync(limit: limit, completion)
case .folder(let folder, let readFilter): case .folder(let folder, let readFilter):
if readFilter { if readFilter {
return fetchUnreadArticlesAsync(folder: folder, completion) return fetchUnreadArticlesAsync(folder: folder, completion)
@ -1046,28 +1046,28 @@ extension Account: WebFeedMetadataDelegate {
private extension Account { private extension Account {
func fetchStarredArticles() throws -> Set<Article> { func fetchStarredArticles(limit: Int?) throws -> Set<Article> {
return try database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs()) return try database.fetchStarredArticles(flattenedWebFeeds().webFeedIDs(), limit)
} }
func fetchStarredArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { func fetchStarredArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), completion) database.fetchedStarredArticlesAsync(flattenedWebFeeds().webFeedIDs(), limit, completion)
} }
func fetchUnreadArticles() throws -> Set<Article> { func fetchUnreadArticles(limit: Int?) throws -> Set<Article> {
return try fetchUnreadArticles(forContainer: self) return try fetchUnreadArticles(forContainer: self, limit: limit)
} }
func fetchUnreadArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { func fetchUnreadArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchUnreadArticlesAsync(forContainer: self, completion) fetchUnreadArticlesAsync(forContainer: self, limit: limit, completion)
} }
func fetchTodayArticles() throws -> Set<Article> { func fetchTodayArticles(limit: Int?) throws -> Set<Article> {
return try database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs()) return try database.fetchTodayArticles(flattenedWebFeeds().webFeedIDs(), limit)
} }
func fetchTodayArticlesAsync(_ completion: @escaping ArticleSetResultBlock) { func fetchTodayArticlesAsync(limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), completion) database.fetchTodayArticlesAsync(flattenedWebFeeds().webFeedIDs(), limit, completion)
} }
func fetchArticles(folder: Folder) throws -> Set<Article> { func fetchArticles(folder: Folder) throws -> Set<Article> {
@ -1079,11 +1079,11 @@ private extension Account {
} }
func fetchUnreadArticles(folder: Folder) throws -> Set<Article> { func fetchUnreadArticles(folder: Folder) throws -> Set<Article> {
return try fetchUnreadArticles(forContainer: folder) return try fetchUnreadArticles(forContainer: folder, limit: nil)
} }
func fetchUnreadArticlesAsync(folder: Folder, _ completion: @escaping ArticleSetResultBlock) { func fetchUnreadArticlesAsync(folder: Folder, _ completion: @escaping ArticleSetResultBlock) {
fetchUnreadArticlesAsync(forContainer: folder, completion) fetchUnreadArticlesAsync(forContainer: folder, limit: nil, completion)
} }
func fetchArticles(webFeed: WebFeed) throws -> Set<Article> { func fetchArticles(webFeed: WebFeed) throws -> Set<Article> {
@ -1129,7 +1129,7 @@ private extension Account {
} }
func fetchUnreadArticles(webFeed: WebFeed) throws -> Set<Article> { func fetchUnreadArticles(webFeed: WebFeed) throws -> Set<Article> {
let articles = try database.fetchUnreadArticles(Set([webFeed.webFeedID])) let articles = try database.fetchUnreadArticles(Set([webFeed.webFeedID]), nil)
validateUnreadCount(webFeed, articles) validateUnreadCount(webFeed, articles)
return articles return articles
} }
@ -1154,16 +1154,16 @@ private extension Account {
} }
} }
func fetchUnreadArticles(forContainer container: Container) throws -> Set<Article> { func fetchUnreadArticles(forContainer container: Container, limit: Int?) throws -> Set<Article> {
let feeds = container.flattenedWebFeeds() let feeds = container.flattenedWebFeeds()
let articles = try database.fetchUnreadArticles(feeds.webFeedIDs()) let articles = try database.fetchUnreadArticles(feeds.webFeedIDs(), limit)
validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles) validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
return articles return articles
} }
func fetchUnreadArticlesAsync(forContainer container: Container, _ completion: @escaping ArticleSetResultBlock) { func fetchUnreadArticlesAsync(forContainer container: Container, limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
let webFeeds = container.flattenedWebFeeds() let webFeeds = container.flattenedWebFeeds()
database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs()) { [weak self] (articleSetResult) in database.fetchUnreadArticlesAsync(webFeeds.webFeedIDs(), limit) { [weak self] (articleSetResult) in
switch articleSetResult { switch articleSetResult {
case .success(let articles): case .success(let articles):
self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles) self?.validateUnreadCountsAfterFetchingUnreadArticles(webFeeds, articles)

@ -102,16 +102,16 @@ public final class ArticlesDatabase {
return try articlesTable.fetchArticles(articleIDs: articleIDs) return try articlesTable.fetchArticles(articleIDs: articleIDs)
} }
public func fetchUnreadArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> { public func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchUnreadArticles(webFeedIDs) return try articlesTable.fetchUnreadArticles(webFeedIDs, limit)
} }
public func fetchTodayArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> { public func fetchTodayArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate()) return try articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate(), limit)
} }
public func fetchStarredArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> { public func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try articlesTable.fetchStarredArticles(webFeedIDs) return try articlesTable.fetchStarredArticles(webFeedIDs, limit)
} }
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> { public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
@ -136,16 +136,16 @@ public final class ArticlesDatabase {
articlesTable.fetchArticlesAsync(articleIDs: articleIDs, completion) articlesTable.fetchArticlesAsync(articleIDs: articleIDs, completion)
} }
public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) { public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchUnreadArticlesAsync(webFeedIDs, completion) articlesTable.fetchUnreadArticlesAsync(webFeedIDs, limit, completion)
} }
public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) { public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), completion) articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), limit, completion)
} }
public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) { public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
articlesTable.fetchStarredArticlesAsync(webFeedIDs, completion) articlesTable.fetchStarredArticlesAsync(webFeedIDs, limit, completion)
} }
public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) { public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {

@ -75,32 +75,32 @@ final class ArticlesTable: DatabaseTable {
// MARK: - Fetching Unread Articles // MARK: - Fetching Unread Articles
func fetchUnreadArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> { func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, $0) } return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, limit, $0) }
} }
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) { func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, $0) }, completion) fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, limit, $0) }, completion)
} }
// MARK: - Fetching Today Articles // MARK: - Fetching Today Articles
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date) throws -> Set<Article> { func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) } return try fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, limit, $0) }
} }
func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ completion: @escaping ArticleSetResultBlock) { func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }, completion) fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, limit, $0) }, completion)
} }
// MARK: - Fetching Starred Articles // MARK: - Fetching Starred Articles
func fetchStarredArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> { func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?) throws -> Set<Article> {
return try fetchArticles{ self.fetchStarredArticles(webFeedIDs, $0) } return try fetchArticles{ self.fetchStarredArticles(webFeedIDs, limit, $0) }
} }
func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) { func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, $0) }, completion) fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, limit, $0) }, completion)
} }
// MARK: - Fetching Search Articles // MARK: - Fetching Search Articles
@ -819,14 +819,17 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters) return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
} }
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> { func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0 // select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
if webFeedIDs.isEmpty { if webFeedIDs.isEmpty {
return Set<Article>() return Set<Article>()
} }
let parameters = webFeedIDs.map { $0 as AnyObject } let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let whereClause = "feedID in \(placeholders) and read=0" var whereClause = "feedID in \(placeholders) and read=0"
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
}
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters) return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
} }
@ -844,7 +847,7 @@ private extension ArticlesTable {
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters) return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
} }
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ database: FMDatabase) -> Set<Article> { func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and (datePublished > ? || (datePublished is null and dateArrived > ?) // select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and (datePublished > ? || (datePublished is null and dateArrived > ?)
// //
// datePublished may be nil, so we fall back to dateArrived. // datePublished may be nil, so we fall back to dateArrived.
@ -853,18 +856,24 @@ private extension ArticlesTable {
} }
let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject] let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?))" var whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?))"
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
}
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters) return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
} }
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> { func fetchStarredArticles(_ webFeedIDs: Set<String>, _ limit: Int?, _ database: FMDatabase) -> Set<Article> {
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1; // select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred=1;
if webFeedIDs.isEmpty { if webFeedIDs.isEmpty {
return Set<Article>() return Set<Article>()
} }
let parameters = webFeedIDs.map { $0 as AnyObject } let parameters = webFeedIDs.map { $0 as AnyObject }
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))! let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let whereClause = "feedID in \(placeholders) and starred=1" var whereClause = "feedID in \(placeholders) and starred=1"
if let limit = limit {
whereClause.append(" order by coalesce(datePublished, dateModified, dateArrived) desc limit \(limit)")
}
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters) return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters)
} }

@ -21,7 +21,7 @@ struct StarredFeedDelegate: SmartFeedDelegate {
} }
let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title") let nameForDisplay = NSLocalizedString("Starred", comment: "Starred pseudo-feed title")
let fetchType: FetchType = .starred let fetchType: FetchType = .starred(nil)
var smallIcon: IconImage? { var smallIcon: IconImage? {
return AppAssets.starredFeedImage return AppAssets.starredFeedImage
} }

@ -19,7 +19,7 @@ struct TodayFeedDelegate: SmartFeedDelegate {
} }
let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title") let nameForDisplay = NSLocalizedString("Today", comment: "Today pseudo-feed title")
let fetchType = FetchType.today let fetchType = FetchType.today(nil)
var smallIcon: IconImage? { var smallIcon: IconImage? {
return AppAssets.todayFeedImage return AppAssets.todayFeedImage
} }

@ -29,7 +29,7 @@ final class UnreadFeed: PseudoFeed {
} }
let nameForDisplay = NSLocalizedString("All Unread", comment: "All Unread pseudo-feed title") let nameForDisplay = NSLocalizedString("All Unread", comment: "All Unread pseudo-feed title")
let fetchType = FetchType.unread let fetchType = FetchType.unread(nil)
var unreadCount = 0 { var unreadCount = 0 {
didSet { didSet {