Move the operation queue from Account to ArticlesDatabase, which is the rightful owner.

This commit is contained in:
Brent Simmons 2020-02-05 22:17:32 -08:00
parent d40eaed1f5
commit 01fc60916e
5 changed files with 64 additions and 69 deletions

View File

@ -231,14 +231,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
return delegate.refreshProgress return delegate.refreshProgress
} }
private var operationQueue = MainThreadOperationQueue()
private enum OperationName {
static let FetchAllUnreadCounts = "FetchAllUnreadCounts"
static let FetchFeedUnreadCount = "FetchFeedUnreadCount"
static let FetchUnreadCountsForFeeds = "FetchUnreadCountsForFeeds"
}
private static let discardableOperationNames = [OperationName.FetchAllUnreadCounts, OperationName.FetchFeedUnreadCount, OperationName.FetchUnreadCountsForFeeds]
init?(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) { init?(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) {
switch type { switch type {
case .onMyMac: case .onMyMac:
@ -422,9 +414,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
} }
public func suspendDatabase() { public func suspendDatabase() {
operationQueue.suspend() database.cancelAndSuspend()
cancelDiscardableOperations()
database.suspend()
save() save()
metadataFile.suspend() metadataFile.suspend()
webFeedMetadataFile.suspend() webFeedMetadataFile.suspend()
@ -436,7 +426,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
public func resumeDatabaseAndDelegate() { public func resumeDatabaseAndDelegate() {
database.resume() database.resume()
delegate.resume() delegate.resume()
operationQueue.resume()
} }
/// Reload OPML, etc. /// Reload OPML, etc.
@ -460,12 +449,6 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
delegate.accountWillBeDeleted(self) delegate.accountWillBeDeleted(self)
} }
public func cancelDiscardableOperations() {
for operationName in Self.discardableOperationNames {
operationQueue.cancelOperations(named: operationName)
}
}
func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) { func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) {
var feedsToAdd = Set<WebFeed>() var feedsToAdd = Set<WebFeed>()
@ -1242,43 +1225,34 @@ private extension Account {
fetchUnreadCounts(feeds, completion) fetchUnreadCounts(feeds, completion)
} }
else { else {
fetchAllUnreadCounts() fetchAllUnreadCounts(completion)
} }
} }
func fetchUnreadCount(_ feed: WebFeed, _ completion: VoidCompletionBlock?) { func fetchUnreadCount(_ feed: WebFeed, _ completion: VoidCompletionBlock?) {
let operation = database.createFetchFeedUnreadCountOperation(feedID: feed.webFeedID) database.fetchUnreadCount(feed.webFeedID) { result in
operation.name = OperationName.FetchFeedUnreadCount if let unreadCount = try? result.get() {
operation.completionBlock = { operation in
let fetchOperation = operation as! FetchFeedUnreadCountOperation
if let unreadCount = fetchOperation.unreadCount {
feed.unreadCount = unreadCount feed.unreadCount = unreadCount
} }
completion?() completion?()
} }
operationQueue.add(operation)
} }
func fetchUnreadCounts(_ feeds: Set<WebFeed>, _ completion: VoidCompletionBlock?) { func fetchUnreadCounts(_ feeds: Set<WebFeed>, _ completion: VoidCompletionBlock?) {
let feedIDs = feeds.map { $0.webFeedID } let webFeedIDs = Set(feeds.map { $0.webFeedID })
let operation = database.createFetchUnreadCountsForFeedsOperation(feedIDs: Set(feedIDs)) database.fetchUnreadCounts(for: webFeedIDs) { result in
operation.name = OperationName.FetchUnreadCountsForFeeds if let unreadCountDictionary = try? result.get() {
operation.completionBlock = { operation in
let fetchOperation = operation as! FetchUnreadCountsForFeedsOperation
if let unreadCountDictionary = fetchOperation.unreadCountDictionary {
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: feeds) self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: feeds)
} }
completion?() completion?()
} }
operationQueue.add(operation)
} }
func fetchAllUnreadCounts() { func fetchAllUnreadCounts(_ completion: VoidCompletionBlock? = nil) {
fetchingAllUnreadCounts = true fetchingAllUnreadCounts = true
database.fetchAllUnreadCounts { (result) in database.fetchAllUnreadCounts { result in
guard let unreadCountDictionary = try? result.get() else { guard let unreadCountDictionary = try? result.get() else {
completion?()
return return
} }
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedWebFeeds()) self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedWebFeeds())
@ -1290,6 +1264,7 @@ private extension Account {
self.isUnreadCountsInitialized = true self.isUnreadCountsInitialized = true
self.postUnreadCountDidInitializeNotification() self.postUnreadCountDidInitializeNotification()
} }
completion?()
} }
} }

View File

@ -148,7 +148,27 @@ public final class ArticlesDatabase {
} }
operationQueue.add(operation) operationQueue.add(operation)
} }
/// Fetch unread count for a single feed.
public func fetchUnreadCount(_ webFeedID: String, _ completion: @escaping SingleUnreadCountCompletionBlock) {
let operation = FetchFeedUnreadCountOperation(webFeedID: webFeedID, databaseQueue: queue, cutoffDate: articlesTable.articleCutoffDate)
operation.completionBlock = { operation in
let fetchOperation = operation as! FetchFeedUnreadCountOperation
completion(fetchOperation.result)
}
operationQueue.add(operation)
}
/// Fetch non-zero unread counts for given webFeedIDs.
public func fetchUnreadCounts(for webFeedIDs: Set<String>, _ completion: @escaping UnreadCountDictionaryCompletionBlock) {
let operation = FetchUnreadCountsForFeedsOperation(webFeedIDs: webFeedIDs, databaseQueue: queue, cutoffDate: articlesTable.articleCutoffDate)
operation.completionBlock = { operation in
let fetchOperation = operation as! FetchUnreadCountsForFeedsOperation
completion(fetchOperation.result)
}
operationQueue.add(operation)
}
public func fetchUnreadCountForToday(for webFeedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) { public func fetchUnreadCountForToday(for webFeedIDs: Set<String>, completion: @escaping SingleUnreadCountCompletionBlock) {
fetchUnreadCount(for: webFeedIDs, since: todayCutoffDate(), completion: completion) fetchUnreadCount(for: webFeedIDs, since: todayCutoffDate(), completion: completion)
} }
@ -199,33 +219,25 @@ public final class ArticlesDatabase {
articlesTable.createStatusesIfNeeded(articleIDs, completion) articlesTable.createStatusesIfNeeded(articleIDs, completion)
} }
// MARK: - Operations
public func cancelOperations() {
operationQueue.cancelAllOperations()
}
/// Create an operation that fetches the unread count for a single given feedID.
public func createFetchFeedUnreadCountOperation(feedID: String) -> FetchFeedUnreadCountOperation {
return FetchFeedUnreadCountOperation(feedID: feedID, databaseQueue: queue, cutoffDate: articlesTable.articleCutoffDate)
}
/// Create an operation that fetches unread counts for a number of feedIDs.
public func createFetchUnreadCountsForFeedsOperation(feedIDs: Set<String>) -> FetchUnreadCountsForFeedsOperation {
return FetchUnreadCountsForFeedsOperation(feedIDs: feedIDs, databaseQueue: queue, cutoffDate: articlesTable.articleCutoffDate)
}
// MARK: - Suspend and Resume (for iOS) // MARK: - Suspend and Resume (for iOS)
/// Cancel current operations and close the database.
public func cancelAndSuspend() {
cancelOperations()
suspend()
}
/// Close the database and stop running database calls. /// Close the database and stop running database calls.
/// Any pending calls will complete first. /// Any pending calls will complete first.
public func suspend() { public func suspend() {
operationQueue.suspend()
queue.suspend() queue.suspend()
} }
/// Open the database and allow for running database calls again. /// Open the database and allow for running database calls again.
public func resume() { public func resume() {
queue.resume() queue.resume()
operationQueue.resume()
} }
// MARK: - Caches // MARK: - Caches
@ -272,4 +284,10 @@ private extension ArticlesDatabase {
// 24 hours previous. This is used by the Today smart feed, which should not actually empty out at midnight. // 24 hours previous. This is used by the Today smart feed, which should not actually empty out at midnight.
return Date(timeIntervalSinceNow: -(60 * 60 * 24)) // This does not need to be more precise. return Date(timeIntervalSinceNow: -(60 * 60 * 24)) // This does not need to be more precise.
} }
// MARK: - Operations
func cancelOperations() {
operationQueue.cancelAllOperations()
}
} }

View File

@ -12,7 +12,7 @@ import RSDatabase
public final class FetchAllUnreadCountsOperation: MainThreadOperation { public final class FetchAllUnreadCountsOperation: MainThreadOperation {
public var result: UnreadCountDictionaryCompletionResult = .failure(.isSuspended) var result: UnreadCountDictionaryCompletionResult = .failure(.isSuspended)
// MainThreadOperation // MainThreadOperation
public var isCanceled = false public var isCanceled = false

View File

@ -13,8 +13,7 @@ import RSDatabase
/// Fetch the unread count for a single feed. /// Fetch the unread count for a single feed.
public final class FetchFeedUnreadCountOperation: MainThreadOperation { public final class FetchFeedUnreadCountOperation: MainThreadOperation {
public var unreadCount: Int? var result: SingleUnreadCountResult = .failure(.isSuspended)
public let feedID: String
// MainThreadOperation // MainThreadOperation
public var isCanceled = false public var isCanceled = false
@ -25,9 +24,10 @@ public final class FetchFeedUnreadCountOperation: MainThreadOperation {
private let queue: DatabaseQueue private let queue: DatabaseQueue
private let cutoffDate: Date private let cutoffDate: Date
private let webFeedID: String
init(feedID: String, databaseQueue: DatabaseQueue, cutoffDate: Date) { init(webFeedID: String, databaseQueue: DatabaseQueue, cutoffDate: Date) {
self.feedID = feedID self.webFeedID = webFeedID
self.queue = databaseQueue self.queue = databaseQueue
self.cutoffDate = cutoffDate self.cutoffDate = cutoffDate
} }
@ -54,7 +54,7 @@ private extension FetchFeedUnreadCountOperation {
func fetchUnreadCount(_ database: FMDatabase) { func fetchUnreadCount(_ database: FMDatabase) {
let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0 and (starred=1 or dateArrived>?);" let sql = "select count(*) from articles natural join statuses where feedID=? and read=0 and userDeleted=0 and (starred=1 or dateArrived>?);"
guard let resultSet = database.executeQuery(sql, withArgumentsIn: [feedID, cutoffDate]) else { guard let resultSet = database.executeQuery(sql, withArgumentsIn: [webFeedID, cutoffDate]) else {
informOperationDelegateOfCompletion() informOperationDelegateOfCompletion()
return return
} }
@ -64,7 +64,8 @@ private extension FetchFeedUnreadCountOperation {
} }
if resultSet.next() { if resultSet.next() {
unreadCount = resultSet.long(forColumnIndex: 0) let unreadCount = resultSet.long(forColumnIndex: 0)
result = .success(unreadCount)
} }
resultSet.close() resultSet.close()

View File

@ -13,8 +13,7 @@ import RSDatabase
/// Fetch the unread counts for a number of feeds. /// Fetch the unread counts for a number of feeds.
public final class FetchUnreadCountsForFeedsOperation: MainThreadOperation { public final class FetchUnreadCountsForFeedsOperation: MainThreadOperation {
public var unreadCountDictionary: UnreadCountDictionary? var result: UnreadCountDictionaryCompletionResult = .failure(.isSuspended)
public let feedIDs: Set<String>
// MainThreadOperation // MainThreadOperation
public var isCanceled = false public var isCanceled = false
@ -25,9 +24,10 @@ public final class FetchUnreadCountsForFeedsOperation: MainThreadOperation {
private let queue: DatabaseQueue private let queue: DatabaseQueue
private let cutoffDate: Date private let cutoffDate: Date
private let webFeedIDs: Set<String>
init(feedIDs: Set<String>, databaseQueue: DatabaseQueue, cutoffDate: Date) { init(webFeedIDs: Set<String>, databaseQueue: DatabaseQueue, cutoffDate: Date) {
self.feedIDs = feedIDs self.webFeedIDs = webFeedIDs
self.queue = databaseQueue self.queue = databaseQueue
self.cutoffDate = cutoffDate self.cutoffDate = cutoffDate
} }
@ -52,11 +52,11 @@ public final class FetchUnreadCountsForFeedsOperation: MainThreadOperation {
private extension FetchUnreadCountsForFeedsOperation { private extension FetchUnreadCountsForFeedsOperation {
func fetchUnreadCounts(_ database: FMDatabase) { func fetchUnreadCounts(_ database: FMDatabase) {
let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(feedIDs.count))! let placeholders = NSString.rs_SQLValueList(withPlaceholders: UInt(webFeedIDs.count))!
let sql = "select distinct feedID, count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and userDeleted=0 and (starred=1 or dateArrived>?) group by feedID;" let sql = "select distinct feedID, count(*) from articles natural join statuses where feedID in \(placeholders) and read=0 and userDeleted=0 and (starred=1 or dateArrived>?) group by feedID;"
var parameters = [Any]() var parameters = [Any]()
parameters += Array(feedIDs) as [Any] parameters += Array(webFeedIDs) as [Any]
parameters += [cutoffDate] as [Any] parameters += [cutoffDate] as [Any]
guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else { guard let resultSet = database.executeQuery(sql, withArgumentsIn: parameters) else {
@ -64,11 +64,12 @@ private extension FetchUnreadCountsForFeedsOperation {
return return
} }
if isCanceled { if isCanceled {
resultSet.close()
informOperationDelegateOfCompletion() informOperationDelegateOfCompletion()
return return
} }
var d = UnreadCountDictionary() var unreadCountDictionary = UnreadCountDictionary()
while resultSet.next() { while resultSet.next() {
if isCanceled { if isCanceled {
resultSet.close() resultSet.close()
@ -77,12 +78,12 @@ private extension FetchUnreadCountsForFeedsOperation {
} }
let unreadCount = resultSet.long(forColumnIndex: 1) let unreadCount = resultSet.long(forColumnIndex: 1)
if let webFeedID = resultSet.string(forColumnIndex: 0) { if let webFeedID = resultSet.string(forColumnIndex: 0) {
d[webFeedID] = unreadCount unreadCountDictionary[webFeedID] = unreadCount
} }
} }
resultSet.close() resultSet.close()
unreadCountDictionary = d result = .success(unreadCountDictionary)
informOperationDelegateOfCompletion() informOperationDelegateOfCompletion()
} }
} }