mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2024-12-24 00:30:51 +01:00
Update Articles.framework to use revised DatabaseQueue — use Result types and throwing functions.
This commit is contained in:
parent
9cb0bcbc94
commit
df45484205
@ -17,17 +17,40 @@ import Articles
|
||||
|
||||
// Main thread only.
|
||||
|
||||
/// Sync methods may throw this error. Async methods use a result type which will include
|
||||
/// this error when the database is suspended and therefore not available.
|
||||
public enum ArticlesDatabaseError: Error {
|
||||
case databaseIsSuspended
|
||||
}
|
||||
|
||||
public typealias UnreadCountDictionary = [String: Int] // webFeedID: unreadCount
|
||||
public typealias UnreadCountCompletionBlock = (UnreadCountDictionary) -> Void
|
||||
public typealias UpdateArticlesCompletionBlock = (Set<Article>?, Set<Article>?) -> Void //newArticles, updatedArticles
|
||||
public typealias UnreadCountDictionaryCompletionResult = Result<UnreadCountDictionary, ArticlesDatabaseError>
|
||||
public typealias UnreadCountDictionaryCompletionBlock = (UnreadCountDictionaryCompletionResult) -> Void
|
||||
|
||||
public typealias SingleUnreadCountResult = Result<Int, ArticlesDatabaseError>
|
||||
public typealias SingleUnreadCountCompletionBlock = (SingleUnreadCountResult) -> Void
|
||||
|
||||
public struct NewAndUpdatedArticles {
|
||||
let newArticles: Set<Article>?
|
||||
let updatedArticles: Set<Article>?
|
||||
}
|
||||
|
||||
public typealias UpdateArticlesResult = Result<NewAndUpdatedArticles, ArticlesDatabaseError>
|
||||
public typealias UpdateArticlesCompletionBlock = (UpdateArticlesResult) -> Void
|
||||
|
||||
public typealias ArticleSetResult = Result<Set<Article>, ArticlesDatabaseError>
|
||||
public typealias ArticleSetResultBlock = (ArticleSetResult) -> Void
|
||||
|
||||
public typealias DatabaseCompletionBlock = (ArticlesDatabaseError?) -> Void
|
||||
|
||||
public typealias ArticleIDsResult = Result<Set<String>, ArticlesDatabaseError>
|
||||
public typealias ArticleIDsCompletionBlock = (ArticleIDsResult) -> Void
|
||||
|
||||
public typealias ArticleStatusesResult = Result<Set<ArticleStatus>, ArticlesDatabaseError>
|
||||
public typealias ArticleStatusesResultBlock = (ArticleStatusesResult) -> Void
|
||||
|
||||
public final class ArticlesDatabase {
|
||||
|
||||
/// When ArticlesDatabase is suspended, database calls will crash the app.
|
||||
public var isSuspended: Bool {
|
||||
return queue.isSuspended
|
||||
}
|
||||
|
||||
private let articlesTable: ArticlesTable
|
||||
private let queue: DatabaseQueue
|
||||
|
||||
@ -54,94 +77,94 @@ public final class ArticlesDatabase {
|
||||
|
||||
// MARK: - Fetching Articles
|
||||
|
||||
public func fetchArticles(_ webFeedID: String) -> Set<Article> {
|
||||
return articlesTable.fetchArticles(webFeedID)
|
||||
public func fetchArticles(_ webFeedID: String) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticles(webFeedID)
|
||||
}
|
||||
|
||||
public func fetchArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticles(webFeedIDs)
|
||||
public func fetchArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticles(webFeedIDs)
|
||||
}
|
||||
|
||||
public func fetchArticles(articleIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticles(articleIDs: articleIDs)
|
||||
public func fetchArticles(articleIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticles(articleIDs: articleIDs)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchUnreadArticles(webFeedIDs)
|
||||
public func fetchUnreadArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchUnreadArticles(webFeedIDs)
|
||||
}
|
||||
|
||||
public func fetchTodayArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate())
|
||||
public func fetchTodayArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticlesSince(webFeedIDs, todayCutoffDate())
|
||||
}
|
||||
|
||||
public func fetchStarredArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchStarredArticles(webFeedIDs)
|
||||
public func fetchStarredArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchStarredArticles(webFeedIDs)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticlesMatching(searchString, webFeedIDs)
|
||||
public func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticlesMatching(searchString, webFeedIDs)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) -> Set<Article> {
|
||||
return articlesTable.fetchArticlesMatchingWithArticleIDs(searchString, articleIDs)
|
||||
public func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) throws -> Set<Article> {
|
||||
return try articlesTable.fetchArticlesMatchingWithArticleIDs(searchString, articleIDs)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Articles Async
|
||||
|
||||
public func fetchArticlesAsync(_ webFeedID: String, _ completion: @escaping ArticleSetBlock) {
|
||||
public func fetchArticlesAsync(_ webFeedID: String, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesAsync(webFeedID, completion)
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
public func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesAsync(webFeedIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
public func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesAsync(articleIDs: articleIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
public func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchUnreadArticlesAsync(webFeedIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
public func fetchTodayArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesSinceAsync(webFeedIDs, todayCutoffDate(), completion)
|
||||
}
|
||||
|
||||
public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
public func fetchedStarredArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchStarredArticlesAsync(webFeedIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
public func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesMatchingAsync(searchString, webFeedIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
public func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
articlesTable.fetchArticlesMatchingWithArticleIDsAsync(searchString, articleIDs, completion)
|
||||
}
|
||||
|
||||
// MARK: - Unread Counts
|
||||
|
||||
public func fetchUnreadCounts(for webFeedIDs: Set<String>, _ completion: @escaping UnreadCountCompletionBlock) {
|
||||
public func fetchUnreadCounts(for webFeedIDs: Set<String>, _ completion: @escaping UnreadCountDictionaryCompletionBlock) {
|
||||
articlesTable.fetchUnreadCounts(webFeedIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForToday(for webFeedIDs: Set<String>, completion: @escaping (Int) -> Void) {
|
||||
public func fetchAllNonZeroUnreadCounts(_ completion: @escaping UnreadCountDictionaryCompletionBlock) {
|
||||
articlesTable.fetchAllUnreadCounts(completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadCountForToday(for webFeedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
fetchUnreadCount(for: webFeedIDs, since: todayCutoffDate(), completion: completion)
|
||||
}
|
||||
|
||||
public func fetchUnreadCount(for webFeedIDs: Set<String>, since: Date, completion: @escaping (Int) -> Void) {
|
||||
public func fetchUnreadCount(for webFeedIDs: Set<String>, since: Date, completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
articlesTable.fetchUnreadCount(webFeedIDs, since, completion)
|
||||
}
|
||||
|
||||
public func fetchStarredAndUnreadCount(for webFeedIDs: Set<String>, completion: @escaping (Int) -> Void) {
|
||||
public func fetchStarredAndUnreadCount(for webFeedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
articlesTable.fetchStarredAndUnreadCount(webFeedIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchAllNonZeroUnreadCounts(_ completion: @escaping UnreadCountCompletionBlock) {
|
||||
articlesTable.fetchAllUnreadCounts(completion)
|
||||
}
|
||||
|
||||
// MARK: - Saving and Updating Articles
|
||||
|
||||
/// Update articles and save new ones. The key for ewbFeedIDsAndItems is webFeedID.
|
||||
@ -149,31 +172,31 @@ public final class ArticlesDatabase {
|
||||
articlesTable.update(webFeedIDsAndItems, defaultRead, completion)
|
||||
}
|
||||
|
||||
public func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool, completion: VoidCompletionBlock? = nil) {
|
||||
public func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool, completion: DatabaseCompletionBlock? = nil) {
|
||||
articlesTable.ensureStatuses(articleIDs, defaultRead, statusKey, flag, completion: completion)
|
||||
}
|
||||
|
||||
// MARK: - Status
|
||||
|
||||
/// Fetch the articleIDs of unread articles in feeds specified by webFeedIDs.
|
||||
public func fetchUnreadArticleIDsAsync(webFeedIDs: Set<String>, completion: @escaping (Set<String>) -> Void) {
|
||||
public func fetchUnreadArticleIDsAsync(webFeedIDs: Set<String>, completion: @escaping ArticleIDsCompletionBlock) {
|
||||
articlesTable.fetchUnreadArticleIDsAsync(webFeedIDs, completion)
|
||||
}
|
||||
|
||||
/// Fetch the articleIDs of starred articles in feeds specified by webFeedIDs.
|
||||
public func fetchStarredArticleIDsAsync(webFeedIDs: Set<String>, completion: @escaping (Set<String>) -> Void) {
|
||||
public func fetchStarredArticleIDsAsync(webFeedIDs: Set<String>, completion: @escaping ArticleIDsCompletionBlock) {
|
||||
articlesTable.fetchStarredArticleIDsAsync(webFeedIDs, completion)
|
||||
}
|
||||
|
||||
public func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
|
||||
return articlesTable.fetchArticleIDsForStatusesWithoutArticles()
|
||||
public func fetchArticleIDsForStatusesWithoutArticles() throws -> Set<String> {
|
||||
return try articlesTable.fetchArticleIDsForStatusesWithoutArticles()
|
||||
}
|
||||
|
||||
public func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) -> Set<ArticleStatus>? {
|
||||
return articlesTable.mark(articles, statusKey, flag)
|
||||
public func mark(_ articles: Set<Article>, statusKey: ArticleStatus.Key, flag: Bool) throws -> Set<ArticleStatus>? {
|
||||
return try articlesTable.mark(articles, statusKey, flag)
|
||||
}
|
||||
|
||||
public func fetchStatuses(articleIDs: Set<String>, createIfNeeded: Bool, completion: @escaping (Set<ArticleStatus>?) -> Void) {
|
||||
public func fetchStatuses(articleIDs: Set<String>, createIfNeeded: Bool, completion: @escaping ArticleStatusesResultBlock) {
|
||||
articlesTable.fetchStatuses(articleIDs, createIfNeeded, completion)
|
||||
}
|
||||
|
||||
@ -208,6 +231,13 @@ public final class ArticlesDatabase {
|
||||
}
|
||||
}
|
||||
|
||||
func databaseError(with databaseQueueError: DatabaseQueueError) -> ArticlesDatabaseError {
|
||||
switch databaseQueueError {
|
||||
case .isSuspended:
|
||||
return .databaseIsSuspended
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Private
|
||||
|
||||
private extension ArticlesDatabase {
|
||||
|
@ -43,167 +43,101 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Fetching Articles for Feed
|
||||
|
||||
func fetchArticles(_ webFeedID: String) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchArticlesForFeedID(webFeedID, withLimits: true, $0) }
|
||||
func fetchArticles(_ webFeedID: String) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticlesForFeedID(webFeedID, withLimits: true, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ webFeedID: String, _ completion: @escaping ArticleSetBlock) {
|
||||
func fetchArticlesAsync(_ webFeedID: String, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesForFeedID(webFeedID, withLimits: true, $0) }, completion)
|
||||
}
|
||||
|
||||
private func fetchArticlesForFeedID(_ webFeedID: String, withLimits: Bool, _ database: FMDatabase) -> Set<Article> {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject], withLimits: withLimits)
|
||||
func fetchArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticles(webFeedIDs, $0) }
|
||||
}
|
||||
|
||||
func fetchArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchArticles(webFeedIDs, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
func fetchArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticles(webFeedIDs, $0) }, completion)
|
||||
}
|
||||
|
||||
private func fetchArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders)"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: true)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Articles by articleID
|
||||
|
||||
func fetchArticles(articleIDs: Set<String>) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchArticles(articleIDs: articleIDs, $0) }
|
||||
func fetchArticles(articleIDs: Set<String>) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticles(articleIDs: articleIDs, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
func fetchArticlesAsync(articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
return fetchArticlesAsync({ self.fetchArticles(articleIDs: articleIDs, $0) }, completion)
|
||||
}
|
||||
|
||||
private func fetchArticles(articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
if articleIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = articleIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))!
|
||||
let whereClause = "articleID in \(placeholders)"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Unread Articles
|
||||
|
||||
func fetchUnreadArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchUnreadArticles(webFeedIDs, $0) }
|
||||
func fetchUnreadArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchUnreadArticles(webFeedIDs, $0) }
|
||||
}
|
||||
|
||||
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
func fetchUnreadArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchUnreadArticles(webFeedIDs, $0) }, completion)
|
||||
}
|
||||
|
||||
private func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and read=0"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: true)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Today Articles
|
||||
|
||||
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }
|
||||
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }
|
||||
}
|
||||
|
||||
func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ completion: @escaping ArticleSetBlock) {
|
||||
func fetchArticlesSinceAsync(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesSince(webFeedIDs, cutoffDate, $0) }, completion)
|
||||
}
|
||||
|
||||
private func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ 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 > ?)
|
||||
//
|
||||
// datePublished may be nil, so we fall back to dateArrived.
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and userDeleted = 0"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Starred Articles
|
||||
|
||||
func fetchStarredArticles(_ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
return fetchArticles{ self.fetchStarredArticles(webFeedIDs, $0) }
|
||||
func fetchStarredArticles(_ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
return try fetchArticles{ self.fetchStarredArticles(webFeedIDs, $0) }
|
||||
}
|
||||
|
||||
func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
func fetchStarredArticlesAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchStarredArticles(webFeedIDs, $0) }, completion)
|
||||
}
|
||||
|
||||
private func fetchStarredArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred = 1 and userDeleted = 0;
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and starred = 1 and userDeleted = 0"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
|
||||
}
|
||||
|
||||
// MARK: - Fetching Search Articles
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String) -> Set<Article> {
|
||||
func fetchArticlesMatching(_ searchString: String) throws -> Set<Article> {
|
||||
var articles: Set<Article> = Set<Article>()
|
||||
guard !queue.isSuspended else {
|
||||
return articles
|
||||
var error: ArticlesDatabaseError? = nil
|
||||
queue.runInDatabaseSync { (databaseResult) in
|
||||
switch databaseResult {
|
||||
case .success(let database):
|
||||
articles = self.fetchArticlesMatching(searchString, database)
|
||||
case .failure(let databaseQueueError):
|
||||
error = databaseError(with: databaseQueueError)
|
||||
}
|
||||
}
|
||||
queue.runInDatabaseSync { (database) in
|
||||
articles = self.fetchArticlesMatching(searchString, database)
|
||||
if let error = error {
|
||||
throw(error)
|
||||
}
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) -> Set<Article> {
|
||||
var articles = fetchArticlesMatching(searchString)
|
||||
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>) throws -> Set<Article> {
|
||||
var articles = try fetchArticlesMatching(searchString)
|
||||
articles = articles.filter{ webFeedIDs.contains($0.webFeedID) }
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) -> Set<Article> {
|
||||
var articles = fetchArticlesMatching(searchString)
|
||||
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>) throws -> Set<Article> {
|
||||
var articles = try fetchArticlesMatching(searchString)
|
||||
articles = articles.filter{ articleIDs.contains($0.articleID) }
|
||||
return articles
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
func fetchArticlesMatchingAsync(_ searchString: String, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesMatching(searchString, webFeedIDs, $0) }, completion)
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetBlock) {
|
||||
func fetchArticlesMatchingWithArticleIDsAsync(_ searchString: String, _ articleIDs: Set<String>, _ completion: @escaping ArticleSetResultBlock) {
|
||||
fetchArticlesAsync({ self.fetchArticlesMatchingWithArticleIDs(searchString, articleIDs, $0) }, completion)
|
||||
}
|
||||
|
||||
private func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
let articles = fetchArticlesMatching(searchString, database)
|
||||
// TODO: include the feedIDs in the SQL rather than filtering here.
|
||||
return articles.filter{ webFeedIDs.contains($0.webFeedID) }
|
||||
}
|
||||
|
||||
private func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
let articles = fetchArticlesMatching(searchString, database)
|
||||
// TODO: include the articleIDs in the SQL rather than filtering here.
|
||||
return articles.filter{ articleIDs.contains($0.articleID) }
|
||||
}
|
||||
|
||||
// MARK: - Fetching Articles for Indexer
|
||||
|
||||
func fetchArticleSearchInfos(_ articleIDs: Set<String>, in database: FMDatabase) -> Set<ArticleSearchInfo>? {
|
||||
@ -235,7 +169,7 @@ final class ArticlesTable: DatabaseTable {
|
||||
|
||||
func update(_ webFeedIDsAndItems: [String: Set<ParsedItem>], _ read: Bool, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
if webFeedIDsAndItems.isEmpty {
|
||||
completion(nil, nil)
|
||||
callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
@ -253,12 +187,14 @@ final class ArticlesTable: DatabaseTable {
|
||||
articleIDs.formUnion(parsedItems.articleIDs())
|
||||
}
|
||||
|
||||
guard !self.queue.isSuspended else {
|
||||
self.callUpdateArticlesCompletionBlock(nil, nil, completion)
|
||||
return
|
||||
}
|
||||
|
||||
self.queue.runInTransaction { (database) in
|
||||
self.queue.runInTransaction { (databaseResult) in
|
||||
guard let database = databaseResult.database else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError(with: databaseResult.error!)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, read, database) //1
|
||||
assert(statusesDictionary.count == articleIDs.count)
|
||||
|
||||
@ -293,31 +229,35 @@ final class ArticlesTable: DatabaseTable {
|
||||
}
|
||||
}
|
||||
|
||||
func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool, completion: VoidCompletionBlock? = nil) {
|
||||
guard !queue.isSuspended else {
|
||||
if let handler = completion {
|
||||
callVoidCompletionBlock(handler)
|
||||
func ensureStatuses(_ articleIDs: Set<String>, _ defaultRead: Bool, _ statusKey: ArticleStatus.Key, _ flag: Bool, completion: DatabaseCompletionBlock? = nil) {
|
||||
queue.runInTransaction { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
DispatchQueue.main.async {
|
||||
completion?(databaseError(with: databaseResult.error!))
|
||||
}
|
||||
return
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
queue.runInTransaction { (database) in
|
||||
|
||||
let statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, defaultRead, database)
|
||||
let statuses = Set(statusesDictionary.values)
|
||||
self.statusesTable.mark(statuses, statusKey, flag, database)
|
||||
if let handler = completion {
|
||||
callVoidCompletionBlock(handler)
|
||||
if let completion = completion {
|
||||
DispatchQueue.main.async {
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchStatuses(_ articleIDs: Set<String>, _ createIfNeeded: Bool, _ completion: @escaping (Set<ArticleStatus>?) -> Void) {
|
||||
guard !queue.isSuspended else {
|
||||
completion(nil)
|
||||
return
|
||||
}
|
||||
func fetchStatuses(_ articleIDs: Set<String>, _ createIfNeeded: Bool, _ completion: @escaping ArticleStatusesResultBlock) {
|
||||
queue.runInTransaction { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError(with: databaseResult.error!)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
queue.runInTransaction { (database) in
|
||||
var statusesDictionary = [String: ArticleStatus]()
|
||||
if createIfNeeded {
|
||||
statusesDictionary = self.statusesTable.ensureStatusesForArticleIDs(articleIDs, false, database)
|
||||
@ -327,45 +267,53 @@ final class ArticlesTable: DatabaseTable {
|
||||
}
|
||||
let statuses = Set(statusesDictionary.values)
|
||||
DispatchQueue.main.async {
|
||||
completion(statuses)
|
||||
completion(.success(statuses))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Unread Counts
|
||||
|
||||
func fetchUnreadCounts(_ webFeedIDs: Set<String>, _ completion: @escaping UnreadCountCompletionBlock) {
|
||||
func fetchUnreadCounts(_ webFeedIDs: Set<String>, _ completion: @escaping UnreadCountDictionaryCompletionBlock) {
|
||||
if webFeedIDs.isEmpty {
|
||||
completion(UnreadCountDictionary())
|
||||
completion(.success(UnreadCountDictionary()))
|
||||
return
|
||||
}
|
||||
|
||||
var unreadCountDictionary = UnreadCountDictionary()
|
||||
guard !queue.isSuspended else {
|
||||
completion(unreadCountDictionary)
|
||||
return
|
||||
}
|
||||
|
||||
queue.runInDatabase { (database) in
|
||||
queue.runInDatabase { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError(with: databaseResult.error!)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
var unreadCountDictionary = UnreadCountDictionary()
|
||||
for webFeedID in webFeedIDs {
|
||||
unreadCountDictionary[webFeedID] = self.fetchUnreadCount(webFeedID, database)
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(unreadCountDictionary)
|
||||
completion(.success(unreadCountDictionary))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUnreadCount(_ webFeedIDs: Set<String>, _ since: Date, _ completion: @escaping (Int) -> Void) {
|
||||
func fetchUnreadCount(_ webFeedIDs: Set<String>, _ since: Date, _ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
// Get unread count for today, for instance.
|
||||
|
||||
if webFeedIDs.isEmpty || queue.isSuspended {
|
||||
completion(0)
|
||||
if webFeedIDs.isEmpty {
|
||||
completion(.success(0))
|
||||
return
|
||||
}
|
||||
|
||||
queue.runInDatabase { (database) in
|
||||
queue.runInDatabase { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError(with: databaseResult.error!)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and read=0 and userDeleted=0;"
|
||||
|
||||
@ -377,27 +325,28 @@ final class ArticlesTable: DatabaseTable {
|
||||
let unreadCount = self.numberWithSQLAndParameters(sql, parameters, in: database)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(unreadCount)
|
||||
completion(.success(unreadCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchAllUnreadCounts(_ completion: @escaping UnreadCountCompletionBlock) {
|
||||
func fetchAllUnreadCounts(_ completion: @escaping UnreadCountDictionaryCompletionBlock) {
|
||||
// Returns only where unreadCount > 0.
|
||||
|
||||
let cutoffDate = articleCutoffDate
|
||||
queue.runInDatabase { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError(with: databaseResult.error!)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
guard !queue.isSuspended else {
|
||||
completion(UnreadCountDictionary())
|
||||
return
|
||||
}
|
||||
|
||||
queue.runInDatabase { (database) in
|
||||
let sql = "select distinct feedID, count(*) from articles natural join statuses where read=0 and userDeleted=0 and (starred=1 or (datePublished > ? or (datePublished is null and dateArrived > ?))) group by feedID;"
|
||||
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: [cutoffDate, cutoffDate]) else {
|
||||
DispatchQueue.main.async {
|
||||
completion(UnreadCountDictionary())
|
||||
completion(.success(UnreadCountDictionary()))
|
||||
}
|
||||
return
|
||||
}
|
||||
@ -411,18 +360,25 @@ final class ArticlesTable: DatabaseTable {
|
||||
}
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(d)
|
||||
completion(.success(d))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchStarredAndUnreadCount(_ webFeedIDs: Set<String>, _ completion: @escaping (Int) -> Void) {
|
||||
if webFeedIDs.isEmpty || queue.isSuspended {
|
||||
completion(0)
|
||||
func fetchStarredAndUnreadCount(_ webFeedIDs: Set<String>, _ completion: @escaping SingleUnreadCountCompletionBlock) {
|
||||
if webFeedIDs.isEmpty {
|
||||
completion(.success(0))
|
||||
return
|
||||
}
|
||||
|
||||
queue.runInDatabase { (database) in
|
||||
queue.runInDatabase { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError(with: databaseResult.error!)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let sql = "select count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and starred=1 and userDeleted=0;"
|
||||
let parameters = Array(webFeedIDs) as [Any]
|
||||
@ -430,50 +386,54 @@ final class ArticlesTable: DatabaseTable {
|
||||
let unreadCount = self.numberWithSQLAndParameters(sql, parameters, in: database)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
completion(unreadCount)
|
||||
completion(.success(unreadCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Statuses
|
||||
|
||||
func fetchUnreadArticleIDsAsync(_ webFeedIDs: Set<String>, _ completion: @escaping (Set<String>) -> Void) {
|
||||
func fetchUnreadArticleIDsAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
fetchArticleIDsAsync(.read, false, webFeedIDs, completion)
|
||||
}
|
||||
|
||||
func fetchStarredArticleIDsAsync(_ webFeedIDs: Set<String>, _ completion: @escaping (Set<String>) -> Void) {
|
||||
func fetchStarredArticleIDsAsync(_ webFeedIDs: Set<String>, _ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
fetchArticleIDsAsync(.starred, true, webFeedIDs, completion)
|
||||
}
|
||||
|
||||
func fetchStarredArticleIDs() -> Set<String> {
|
||||
return statusesTable.fetchStarredArticleIDs()
|
||||
func fetchStarredArticleIDs() throws -> Set<String> {
|
||||
return try statusesTable.fetchStarredArticleIDs()
|
||||
}
|
||||
|
||||
func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
|
||||
return statusesTable.fetchArticleIDsForStatusesWithoutArticles()
|
||||
func fetchArticleIDsForStatusesWithoutArticles() throws -> Set<String> {
|
||||
return try statusesTable.fetchArticleIDsForStatusesWithoutArticles()
|
||||
}
|
||||
|
||||
func mark(_ articles: Set<Article>, _ statusKey: ArticleStatus.Key, _ flag: Bool) -> Set<ArticleStatus>? {
|
||||
func mark(_ articles: Set<Article>, _ statusKey: ArticleStatus.Key, _ flag: Bool) throws -> Set<ArticleStatus>? {
|
||||
var statuses: Set<ArticleStatus>?
|
||||
|
||||
guard !self.queue.isSuspended else {
|
||||
return statuses
|
||||
var error: ArticlesDatabaseError?
|
||||
self.queue.runInTransactionSync { databaseResult in
|
||||
switch databaseResult {
|
||||
case .success(let database):
|
||||
statuses = self.statusesTable.mark(articles.statuses(), statusKey, flag, database)
|
||||
case .failure(let databaseQueueError):
|
||||
error = databaseError(with: databaseQueueError)
|
||||
}
|
||||
}
|
||||
|
||||
self.queue.runInTransactionSync { (database) in
|
||||
statuses = self.statusesTable.mark(articles.statuses(), statusKey, flag, database)
|
||||
|
||||
if let error = error {
|
||||
throw error
|
||||
}
|
||||
|
||||
return statuses
|
||||
}
|
||||
|
||||
// MARK: - Indexing
|
||||
|
||||
func indexUnindexedArticles() {
|
||||
guard !queue.isSuspended else {
|
||||
return
|
||||
}
|
||||
queue.runInDatabase { (database) in
|
||||
queue.runInDatabase { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
return
|
||||
}
|
||||
let sql = "select articleID from articles where searchRowID is null limit 500;"
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: nil) else {
|
||||
return
|
||||
@ -504,10 +464,14 @@ final class ArticlesTable: DatabaseTable {
|
||||
/// This deletes from the articles and articleStatuses tables,
|
||||
/// and, via a trigger, it also deletes from the search index.
|
||||
func deleteArticlesNotInSubscribedToFeedIDs(_ webFeedIDs: Set<String>) {
|
||||
if webFeedIDs.isEmpty || queue.isSuspended {
|
||||
if webFeedIDs.isEmpty {
|
||||
return
|
||||
}
|
||||
queue.runInDatabase { (database) in
|
||||
queue.runInDatabase { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
return
|
||||
}
|
||||
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let sql = "select articleID from articles where feedID not in \(placeholders);"
|
||||
let parameters = Array(webFeedIDs) as [Any]
|
||||
@ -530,26 +494,35 @@ private extension ArticlesTable {
|
||||
|
||||
// MARK: - Fetching
|
||||
|
||||
private func fetchArticles(_ fetchMethod: @escaping ArticlesFetchMethod) -> Set<Article> {
|
||||
private func fetchArticles(_ fetchMethod: @escaping ArticlesFetchMethod) throws -> Set<Article> {
|
||||
var articles = Set<Article>()
|
||||
guard !queue.isSuspended else {
|
||||
return articles
|
||||
var error: ArticlesDatabaseError? = nil
|
||||
queue.runInDatabaseSync { databaseResult in
|
||||
switch databaseResult {
|
||||
case .success(let database):
|
||||
articles = fetchMethod(database)
|
||||
case .failure(let databaseQueueError):
|
||||
error = databaseError(with: databaseQueueError)
|
||||
}
|
||||
}
|
||||
queue.runInDatabaseSync { (database) in
|
||||
articles = fetchMethod(database)
|
||||
if let error = error {
|
||||
throw(error)
|
||||
}
|
||||
return articles
|
||||
}
|
||||
|
||||
private func fetchArticlesAsync(_ fetchMethod: @escaping ArticlesFetchMethod, _ completion: @escaping ArticleSetBlock) {
|
||||
guard !queue.isSuspended else {
|
||||
completion(Set<Article>())
|
||||
return
|
||||
}
|
||||
queue.runInDatabase { (database) in
|
||||
private func fetchArticlesAsync(_ fetchMethod: @escaping ArticlesFetchMethod, _ completion: @escaping ArticleSetResultBlock) {
|
||||
queue.runInDatabase { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError(with: databaseResult.error!)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let articles = fetchMethod(database)
|
||||
DispatchQueue.main.async {
|
||||
completion(articles)
|
||||
completion(.success(articles))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -702,12 +675,20 @@ private extension ArticlesTable {
|
||||
return articlesWithResultSet(resultSet, database)
|
||||
}
|
||||
|
||||
func fetchArticleIDsAsync(_ statusKey: ArticleStatus.Key, _ value: Bool, _ webFeedIDs: Set<String>, _ completion: @escaping (Set<String>) -> Void) {
|
||||
guard !queue.isSuspended && !webFeedIDs.isEmpty else {
|
||||
completion(Set<String>())
|
||||
func fetchArticleIDsAsync(_ statusKey: ArticleStatus.Key, _ value: Bool, _ webFeedIDs: Set<String>, _ completion: @escaping ArticleIDsCompletionBlock) {
|
||||
guard !webFeedIDs.isEmpty else {
|
||||
completion(.success(Set<String>()))
|
||||
return
|
||||
}
|
||||
queue.runInDatabase { database in
|
||||
|
||||
queue.runInDatabase { databaseResult in
|
||||
guard let database = databaseResult.database else {
|
||||
DispatchQueue.main.async {
|
||||
completion(.failure(databaseError(with: databaseResult.error!)))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
var sql = "select articleID from articles natural join statuses where feedID in \(placeholders) and \(statusKey.rawValue)="
|
||||
sql += value ? "1" : "0"
|
||||
@ -720,24 +701,96 @@ private extension ArticlesTable {
|
||||
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
|
||||
DispatchQueue.main.async {
|
||||
completion(Set<String>())
|
||||
completion(.success(Set<String>()))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
let articleIDs = resultSet.mapToSet{ $0.string(forColumnIndex: 0) }
|
||||
DispatchQueue.main.async {
|
||||
completion(articleIDs)
|
||||
completion(.success(articleIDs))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders)"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: true)
|
||||
}
|
||||
|
||||
func fetchUnreadArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and read=0
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and read=0"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: true)
|
||||
}
|
||||
|
||||
func fetchArticlesForFeedID(_ webFeedID: String, withLimits: Bool, _ database: FMDatabase) -> Set<Article> {
|
||||
return fetchArticlesWithWhereClause(database, whereClause: "articles.feedID = ?", parameters: [webFeedID as AnyObject], withLimits: withLimits)
|
||||
}
|
||||
|
||||
func fetchArticles(articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
if articleIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = articleIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(articleIDs.count))!
|
||||
let whereClause = "articleID in \(placeholders)"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
|
||||
}
|
||||
|
||||
func fetchArticlesSince(_ webFeedIDs: Set<String>, _ cutoffDate: Date, _ 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 > ?)
|
||||
//
|
||||
// datePublished may be nil, so we fall back to dateArrived.
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject } + [cutoffDate as AnyObject, cutoffDate as AnyObject]
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and (datePublished > ? or (datePublished is null and dateArrived > ?)) and userDeleted = 0"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
|
||||
}
|
||||
|
||||
func fetchStarredArticles(_ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
// select * from articles natural join statuses where feedID in ('http://ranchero.com/xml/rss.xml') and starred = 1 and userDeleted = 0;
|
||||
if webFeedIDs.isEmpty {
|
||||
return Set<Article>()
|
||||
}
|
||||
let parameters = webFeedIDs.map { $0 as AnyObject }
|
||||
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
|
||||
let whereClause = "feedID in \(placeholders) and starred = 1 and userDeleted = 0"
|
||||
return fetchArticlesWithWhereClause(database, whereClause: whereClause, parameters: parameters, withLimits: false)
|
||||
}
|
||||
|
||||
func fetchArticlesMatching(_ searchString: String, _ webFeedIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
let articles = fetchArticlesMatching(searchString, database)
|
||||
// TODO: include the feedIDs in the SQL rather than filtering here.
|
||||
return articles.filter{ webFeedIDs.contains($0.webFeedID) }
|
||||
}
|
||||
|
||||
func fetchArticlesMatchingWithArticleIDs(_ searchString: String, _ articleIDs: Set<String>, _ database: FMDatabase) -> Set<Article> {
|
||||
let articles = fetchArticlesMatching(searchString, database)
|
||||
// TODO: include the articleIDs in the SQL rather than filtering here.
|
||||
return articles.filter{ articleIDs.contains($0.articleID) }
|
||||
}
|
||||
|
||||
// MARK: - Saving Parsed Items
|
||||
|
||||
func callUpdateArticlesCompletionBlock(_ newArticles: Set<Article>?, _ updatedArticles: Set<Article>?, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
let newAndUpdatedArticles = NewAndUpdatedArticles(newArticles: newArticles, updatedArticles: updatedArticles)
|
||||
DispatchQueue.main.async {
|
||||
completion(newArticles, updatedArticles)
|
||||
completion(.success(newAndUpdatedArticles))
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -74,11 +74,13 @@ final class SearchTable: DatabaseTable {
|
||||
}
|
||||
|
||||
func ensureIndexedArticles(for articleIDs: Set<String>) {
|
||||
guard !queue.isSuspended && !articleIDs.isEmpty else {
|
||||
guard !articleIDs.isEmpty else {
|
||||
return
|
||||
}
|
||||
queue.runInTransaction { (database) in
|
||||
self.ensureIndexedArticles(articleIDs, database)
|
||||
queue.runInTransaction { databaseResult in
|
||||
if let database = databaseResult.database {
|
||||
self.ensureIndexedArticles(articleIDs, database)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -87,28 +87,34 @@ final class StatusesTable: DatabaseTable {
|
||||
|
||||
// MARK: - Fetching
|
||||
|
||||
func fetchUnreadArticleIDs() -> Set<String> {
|
||||
return fetchArticleIDs("select articleID from statuses where read=0 and userDeleted=0;")
|
||||
func fetchUnreadArticleIDs() throws -> Set<String> {
|
||||
return try fetchArticleIDs("select articleID from statuses where read=0 and userDeleted=0;")
|
||||
}
|
||||
|
||||
func fetchStarredArticleIDs() -> Set<String> {
|
||||
return fetchArticleIDs("select articleID from statuses where starred=1 and userDeleted=0;")
|
||||
func fetchStarredArticleIDs() throws -> Set<String> {
|
||||
return try fetchArticleIDs("select articleID from statuses where starred=1 and userDeleted=0;")
|
||||
}
|
||||
|
||||
func fetchArticleIDsForStatusesWithoutArticles() -> Set<String> {
|
||||
return fetchArticleIDs("select articleID from statuses s where (read=0 or starred=1) and userDeleted=0 and not exists (select 1 from articles a where a.articleID = s.articleID);")
|
||||
func fetchArticleIDsForStatusesWithoutArticles() throws -> Set<String> {
|
||||
return try fetchArticleIDs("select articleID from statuses s where (read=0 or starred=1) and userDeleted=0 and not exists (select 1 from articles a where a.articleID = s.articleID);")
|
||||
}
|
||||
|
||||
func fetchArticleIDs(_ sql: String) -> Set<String> {
|
||||
func fetchArticleIDs(_ sql: String) throws -> Set<String> {
|
||||
var error: ArticlesDatabaseError?
|
||||
var articleIDs = Set<String>()
|
||||
guard !queue.isSuspended else {
|
||||
return articleIDs
|
||||
}
|
||||
queue.runInDatabaseSync { (database) in
|
||||
guard let resultSet = database.executeQuery(sql, withArgumentsIn: nil) else {
|
||||
return
|
||||
queue.runInDatabaseSync { databaseResult in
|
||||
switch databaseResult {
|
||||
case .success(let database):
|
||||
if let resultSet = database.executeQuery(sql, withArgumentsIn: nil) {
|
||||
articleIDs = resultSet.mapToSet(self.articleIDWithRow)
|
||||
}
|
||||
case .failure(let databaseQueueError):
|
||||
error = databaseError(with: databaseQueueError)
|
||||
}
|
||||
articleIDs = resultSet.mapToSet(self.articleIDWithRow)
|
||||
}
|
||||
|
||||
if let error = error {
|
||||
throw(error)
|
||||
}
|
||||
return articleIDs
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user