Make fetching all unread counts an operation. Cancel it when the account is suspending. Turning things like this into operations goes to fixing the dreaded 0xdead10cc crashes.

This commit is contained in:
Brent Simmons 2020-01-27 23:00:48 -08:00
parent 04f4e8655b
commit dc9243dcc7
4 changed files with 53 additions and 35 deletions

View File

@ -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<WebFeed>()
@ -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 wont 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 wont appear in unreadCountDictionary.
let unreadCount = unreadCountDictionary[feed.webFeedID] ?? 0
feed.unreadCount = unreadCount
}
}
}

View File

@ -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<String>, 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.

View File

@ -521,6 +521,10 @@ final class ArticlesTable: DatabaseTable {
}
}
}
func createFetchAllUnreadCountsOperation() -> FetchAllUnreadCountsOperation {
return FetchAllUnreadCountsOperation(databaseQueue: queue, cutoffDate: articleCutoffDate)
}
}
// MARK: - Private

View File

@ -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;"