diff --git a/Frameworks/Account/Account.swift b/Frameworks/Account/Account.swift index 5d367d628..c6d6a220a 100644 --- a/Frameworks/Account/Account.swift +++ b/Frameworks/Account/Account.swift @@ -230,6 +230,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, var refreshProgress: DownloadProgress { return delegate.refreshProgress } + + private var operationQueue = MainThreadOperationQueue() + private enum OperationName { + static let FetchAllUnreadCounts = "FetchAllUnreadCounts" + } init?(dataFolder: String, type: AccountType, accountID: String, transport: Transport? = nil) { switch type { @@ -414,6 +419,8 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, } public func suspendDatabase() { + operationQueue.suspend() + cancelDiscardableOperations() database.suspend() save() metadataFile.suspend() @@ -426,6 +433,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, public func resumeDatabaseAndDelegate() { database.resume() delegate.resume() + operationQueue.resume() } /// Reload OPML, etc. @@ -447,6 +455,10 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container, public func prepareForDeletion() { delegate.accountWillBeDeleted(self) } + + public func cancelDiscardableOperations() { + operationQueue.cancelOperations(named: OperationName.FetchAllUnreadCounts) + } func loadOPMLItems(_ items: [RSOPMLItem], parentFolder: Folder?) { var feedsToAdd = Set() @@ -1226,24 +1238,31 @@ private extension Account { func fetchAllUnreadCounts() { fetchingAllUnreadCounts = true + operationQueue.cancelOperations(named: OperationName.FetchAllUnreadCounts) - database.fetchAllNonZeroUnreadCounts { (unreadCountDictionaryResult) in - if let unreadCountDictionary = try? unreadCountDictionaryResult.get() { - self.flattenedWebFeeds().forEach{ (feed) in - // When the unread count is zero, it won’t appear in unreadCountDictionary. - if let unreadCount = unreadCountDictionary[feed.webFeedID] { - feed.unreadCount = unreadCount - } - else { - feed.unreadCount = 0 - } - } - - self.fetchingAllUnreadCounts = false - self.updateUnreadCount() - self.isUnreadCountsInitialized = true - self.postUnreadCountDidInitializeNotification() + let operation = database.createFetchAllUnreadCountsOperation() + operation.name = OperationName.FetchAllUnreadCounts + operation.completionBlock = { operation in + let fetchOperation = operation as! FetchAllUnreadCountsOperation + guard let unreadCountDictionary = fetchOperation.unreadCountDictionary else { + return } + self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary) + + self.fetchingAllUnreadCounts = false + self.updateUnreadCount() + self.isUnreadCountsInitialized = true + self.postUnreadCountDidInitializeNotification() + } + + operationQueue.addOperation(operation) + } + + func processUnreadCounts(unreadCountDictionary: UnreadCountDictionary) { + for feed in flattenedWebFeeds() { + // When the unread count is zero, it won’t appear in unreadCountDictionary. + let unreadCount = unreadCountDictionary[feed.webFeedID] ?? 0 + feed.unreadCount = unreadCount } } } diff --git a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift index 86b2b2210..e8141ae2e 100644 --- a/Frameworks/ArticlesDatabase/ArticlesDatabase.swift +++ b/Frameworks/ArticlesDatabase/ArticlesDatabase.swift @@ -141,10 +141,6 @@ public final class ArticlesDatabase { articlesTable.fetchUnreadCounts(webFeedIDs, completion) } - public func fetchAllNonZeroUnreadCounts(_ completion: @escaping UnreadCountDictionaryCompletionBlock) { - articlesTable.fetchAllUnreadCounts(completion) - } - public func fetchUnreadCountForToday(for webFeedIDs: Set, completion: @escaping SingleUnreadCountCompletionBlock) { fetchUnreadCount(for: webFeedIDs, since: todayCutoffDate(), completion: completion) } @@ -195,6 +191,13 @@ public final class ArticlesDatabase { articlesTable.createStatusesIfNeeded(articleIDs, completion) } + // MARK: - Operations + + /// Create and return an operation that fetches all non-zero unread counts. + public func createFetchAllUnreadCountsOperation() -> FetchAllUnreadCountsOperation { + return articlesTable.createFetchAllUnreadCountsOperation() + } + // MARK: - Suspend and Resume (for iOS) /// Close the database and stop running database calls. diff --git a/Frameworks/ArticlesDatabase/ArticlesTable.swift b/Frameworks/ArticlesDatabase/ArticlesTable.swift index a537ab9ee..3fe6d7225 100644 --- a/Frameworks/ArticlesDatabase/ArticlesTable.swift +++ b/Frameworks/ArticlesDatabase/ArticlesTable.swift @@ -521,6 +521,10 @@ final class ArticlesTable: DatabaseTable { } } } + + func createFetchAllUnreadCountsOperation() -> FetchAllUnreadCountsOperation { + return FetchAllUnreadCountsOperation(databaseQueue: queue, cutoffDate: articleCutoffDate) + } } // MARK: - Private diff --git a/Frameworks/ArticlesDatabase/Operations/FetchAllUnreadCountsOperation.swift b/Frameworks/ArticlesDatabase/Operations/FetchAllUnreadCountsOperation.swift index eefbf6438..cb172bb68 100644 --- a/Frameworks/ArticlesDatabase/Operations/FetchAllUnreadCountsOperation.swift +++ b/Frameworks/ArticlesDatabase/Operations/FetchAllUnreadCountsOperation.swift @@ -10,16 +10,16 @@ import Foundation import RSCore import RSDatabase -final class FetchAllUnreadCountsOperation: MainThreadOperation { +public final class FetchAllUnreadCountsOperation: MainThreadOperation { public var unreadCountDictionary: UnreadCountDictionary? // MainThreadOperation - var isCanceled = false - var id: Int? - weak var operationDelegate: MainThreadOperationDelegate? - var name: String? = "FetchAllUnreadCountsOperation" - var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock? + public var isCanceled = false + public var id: Int? + public weak var operationDelegate: MainThreadOperationDelegate? + public var name: String? + public var completionBlock: MainThreadOperation.MainThreadOperationCompletionBlock? private let queue: DatabaseQueue private let cutoffDate: Date @@ -29,7 +29,7 @@ final class FetchAllUnreadCountsOperation: MainThreadOperation { self.cutoffDate = cutoffDate } - func run() { + public func run() { queue.runInDatabase { databaseResult in if self.isCanceled { self.informOperationDelegateOfCompletion() @@ -48,14 +48,6 @@ final class FetchAllUnreadCountsOperation: MainThreadOperation { private extension FetchAllUnreadCountsOperation { - func informOperationDelegateOfCompletion() { - DispatchQueue.main.async { - if !self.isCanceled { - self.operationDelegate?.operationDidComplete(self) - } - } - } - func fetchUnreadCounts(_ database: FMDatabase) { let sql = "select distinct feedID, count(*) from articles natural join statuses where read=0 and userDeleted=0 and (starred=1 or dateArrived>?) group by feedID;"