Fix unreadCount-related concurrency issues.
This commit is contained in:
parent
101cf02fec
commit
acd86c9e2a
|
@ -723,7 +723,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
try await database.unreadArticles(feedIDs: Set([feed.feedID]))
|
||||
}
|
||||
|
||||
public func unreadArticles(feeds: Set<Feed>) async throws -> Set<Article> {
|
||||
@MainActor public func unreadArticles(feeds: Set<Feed>) async throws -> Set<Article> {
|
||||
|
||||
if feeds.isEmpty {
|
||||
return Set<Article>()
|
||||
|
@ -1011,19 +1011,19 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
|||
NotificationCenter.default.post(name: .AccountRefreshProgressDidChange, object: self)
|
||||
}
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
@MainActor @objc func unreadCountDidChange(_ note: Notification) {
|
||||
if let feed = note.object as? Feed, feed.account === self {
|
||||
updateUnreadCount()
|
||||
}
|
||||
}
|
||||
|
||||
@objc func batchUpdateDidPerform(_ note: Notification) {
|
||||
@MainActor @objc func batchUpdateDidPerform(_ note: Notification) {
|
||||
flattenedFeedsNeedUpdate = true
|
||||
rebuildFeedDictionaries()
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
@objc func childrenDidChange(_ note: Notification) {
|
||||
@MainActor @objc func childrenDidChange(_ note: Notification) {
|
||||
guard let object = note.object else {
|
||||
return
|
||||
}
|
||||
|
@ -1165,7 +1165,7 @@ private extension Account {
|
|||
return database.fetchArticlesAsync(articleIDs: articleIDs, completion)
|
||||
}
|
||||
|
||||
func articles(container: Container) async throws -> Set<Article> {
|
||||
@MainActor func articles(container: Container) async throws -> Set<Article> {
|
||||
|
||||
let feeds = container.flattenedFeeds()
|
||||
let articles = try await database.articles(feedIDs: allFeedIDs())
|
||||
|
@ -1178,17 +1178,20 @@ private extension Account {
|
|||
func fetchArticlesAsync(forContainer container: Container, _ completion: @escaping ArticleSetResultBlock) {
|
||||
let feeds = container.flattenedFeeds()
|
||||
database.fetchArticlesAsync(feeds.feedIDs()) { [weak self] (articleSetResult) in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
completion(.success(articles))
|
||||
case .failure(let databaseError):
|
||||
completion(.failure(databaseError))
|
||||
|
||||
Task { @MainActor [weak self] in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
completion(.success(articles))
|
||||
case .failure(let databaseError):
|
||||
completion(.failure(databaseError))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func unreadArticles(container: Container, limit: Int? = nil) async throws -> Set<Article> {
|
||||
@MainActor func unreadArticles(container: Container, limit: Int? = nil) async throws -> Set<Article> {
|
||||
|
||||
let feeds = container.flattenedFeeds()
|
||||
let feedIDs = feeds.feedIDs()
|
||||
|
@ -1206,23 +1209,26 @@ private extension Account {
|
|||
func fetchUnreadArticlesAsync(forContainer container: Container, limit: Int?, _ completion: @escaping ArticleSetResultBlock) {
|
||||
let feeds = container.flattenedFeeds()
|
||||
database.fetchUnreadArticlesAsync(feeds.feedIDs(), limit) { [weak self] (articleSetResult) in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
|
||||
// We don't validate limit queries because they, by definition, won't correctly match the
|
||||
// complete unread state for the given container.
|
||||
if limit == nil {
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
|
||||
Task { @MainActor [weak self] in
|
||||
switch articleSetResult {
|
||||
case .success(let articles):
|
||||
|
||||
// We don't validate limit queries because they, by definition, won't correctly match the
|
||||
// complete unread state for the given container.
|
||||
if limit == nil {
|
||||
self?.validateUnreadCountsAfterFetchingUnreadArticles(feeds, articles)
|
||||
}
|
||||
|
||||
completion(.success(articles))
|
||||
case .failure(let databaseError):
|
||||
completion(.failure(databaseError))
|
||||
}
|
||||
|
||||
completion(.success(articles))
|
||||
case .failure(let databaseError):
|
||||
completion(.failure(databaseError))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set<Feed>, _ articles: Set<Article>) {
|
||||
@MainActor func validateUnreadCountsAfterFetchingUnreadArticles(_ feeds: Set<Feed>, _ articles: Set<Article>) {
|
||||
// Validate unread counts. This was the site of a performance slowdown:
|
||||
// it was calling going through the entire list of articles once per feed:
|
||||
// feeds.forEach { validateUnreadCount($0, articles) }
|
||||
|
@ -1302,7 +1308,7 @@ private extension Account {
|
|||
feedDictionariesNeedUpdate = false
|
||||
}
|
||||
|
||||
func updateUnreadCount() {
|
||||
@MainActor func updateUnreadCount() {
|
||||
if fetchingAllUnreadCounts {
|
||||
return
|
||||
}
|
||||
|
@ -1356,44 +1362,52 @@ private extension Account {
|
|||
|
||||
func fetchUnreadCount(_ feed: Feed, _ completion: VoidCompletionBlock?) {
|
||||
database.fetchUnreadCount(feed.feedID) { result in
|
||||
if let unreadCount = try? result.get() {
|
||||
feed.unreadCount = unreadCount
|
||||
Task { @MainActor in
|
||||
if let unreadCount = try? result.get() {
|
||||
feed.unreadCount = unreadCount
|
||||
}
|
||||
completion?()
|
||||
}
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUnreadCounts(_ feeds: Set<Feed>, _ completion: VoidCompletionBlock?) {
|
||||
let feedIDs = Set(feeds.map { $0.feedID })
|
||||
database.fetchUnreadCounts(for: feedIDs) { result in
|
||||
if let unreadCountDictionary = try? result.get() {
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: feeds)
|
||||
|
||||
Task { @MainActor in
|
||||
if let unreadCountDictionary = try? result.get() {
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: feeds)
|
||||
}
|
||||
completion?()
|
||||
}
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
func fetchAllUnreadCounts(_ completion: VoidCompletionBlock? = nil) {
|
||||
fetchingAllUnreadCounts = true
|
||||
database.fetchAllUnreadCounts { result in
|
||||
guard let unreadCountDictionary = try? result.get() else {
|
||||
|
||||
Task { @MainActor in
|
||||
guard let unreadCountDictionary = try? result.get() else {
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedFeeds())
|
||||
|
||||
self.fetchingAllUnreadCounts = false
|
||||
self.updateUnreadCount()
|
||||
|
||||
if !self.isUnreadCountsInitialized {
|
||||
self.isUnreadCountsInitialized = true
|
||||
self.postUnreadCountDidInitializeNotification()
|
||||
}
|
||||
completion?()
|
||||
return
|
||||
}
|
||||
self.processUnreadCounts(unreadCountDictionary: unreadCountDictionary, feeds: self.flattenedFeeds())
|
||||
|
||||
self.fetchingAllUnreadCounts = false
|
||||
self.updateUnreadCount()
|
||||
|
||||
if !self.isUnreadCountsInitialized {
|
||||
self.isUnreadCountsInitialized = true
|
||||
self.postUnreadCountDidInitializeNotification()
|
||||
}
|
||||
completion?()
|
||||
}
|
||||
}
|
||||
|
||||
func processUnreadCounts(unreadCountDictionary: UnreadCountDictionary, feeds: Set<Feed>) {
|
||||
@MainActor func processUnreadCounts(unreadCountDictionary: UnreadCountDictionary, feeds: Set<Feed>) {
|
||||
for feed in feeds {
|
||||
// When the unread count is zero, it won’t appear in unreadCountDictionary.
|
||||
let unreadCount = unreadCountDictionary[feed.feedID] ?? 0
|
||||
|
|
|
@ -148,7 +148,7 @@ public final class AccountManager: UnreadCountProvider {
|
|||
return account
|
||||
}
|
||||
|
||||
public func deleteAccount(_ account: Account) {
|
||||
@MainActor public func deleteAccount(_ account: Account) {
|
||||
guard !account.refreshInProgress else {
|
||||
return
|
||||
}
|
||||
|
@ -409,7 +409,7 @@ public final class AccountManager: UnreadCountProvider {
|
|||
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func unreadCountDidInitialize(_ notification: Notification) {
|
||||
@MainActor @objc func unreadCountDidInitialize(_ notification: Notification) {
|
||||
guard let _ = notification.object as? Account else {
|
||||
return
|
||||
}
|
||||
|
@ -418,14 +418,14 @@ public final class AccountManager: UnreadCountProvider {
|
|||
}
|
||||
}
|
||||
|
||||
@objc dynamic func unreadCountDidChange(_ notification: Notification) {
|
||||
@MainActor @objc dynamic func unreadCountDidChange(_ notification: Notification) {
|
||||
guard let _ = notification.object as? Account else {
|
||||
return
|
||||
}
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
@objc func accountStateDidChange(_ notification: Notification) {
|
||||
@MainActor @objc func accountStateDidChange(_ notification: Notification) {
|
||||
updateUnreadCount()
|
||||
}
|
||||
}
|
||||
|
@ -434,7 +434,7 @@ public final class AccountManager: UnreadCountProvider {
|
|||
|
||||
private extension AccountManager {
|
||||
|
||||
func updateUnreadCount() {
|
||||
@MainActor func updateUnreadCount() {
|
||||
unreadCount = calculateUnreadCount(activeAccounts)
|
||||
}
|
||||
|
||||
|
|
|
@ -74,7 +74,7 @@ public final class Folder: Renamable, Container, DisplayNameProvider, UnreadCoun
|
|||
|
||||
// MARK: - Notifications
|
||||
|
||||
@objc func unreadCountDidChange(_ note: Notification) {
|
||||
@MainActor @objc func unreadCountDidChange(_ note: Notification) {
|
||||
if let object = note.object {
|
||||
if objectIsChild(object as AnyObject) {
|
||||
updateUnreadCount()
|
||||
|
@ -82,7 +82,7 @@ public final class Folder: Renamable, Container, DisplayNameProvider, UnreadCoun
|
|||
}
|
||||
}
|
||||
|
||||
@objc func childrenDidChange(_ note: Notification) {
|
||||
@MainActor @objc func childrenDidChange(_ note: Notification) {
|
||||
updateUnreadCount()
|
||||
}
|
||||
|
||||
|
@ -144,7 +144,7 @@ public final class Folder: Renamable, Container, DisplayNameProvider, UnreadCoun
|
|||
|
||||
private extension Folder {
|
||||
|
||||
func updateUnreadCount() {
|
||||
@MainActor func updateUnreadCount() {
|
||||
var updatedUnreadCount = 0
|
||||
for feed in topLevelFeeds {
|
||||
updatedUnreadCount += feed.unreadCount
|
||||
|
|
|
@ -15,24 +15,24 @@ public extension Notification.Name {
|
|||
|
||||
public protocol UnreadCountProvider {
|
||||
|
||||
var unreadCount: Int { get }
|
||||
@MainActor var unreadCount: Int { get }
|
||||
|
||||
func postUnreadCountDidChangeNotification()
|
||||
func calculateUnreadCount<T: Collection>(_ children: T) -> Int
|
||||
@MainActor func postUnreadCountDidChangeNotification()
|
||||
@MainActor func calculateUnreadCount<T: Collection>(_ children: T) -> Int
|
||||
}
|
||||
|
||||
|
||||
public extension UnreadCountProvider {
|
||||
|
||||
func postUnreadCountDidInitializeNotification() {
|
||||
@MainActor func postUnreadCountDidInitializeNotification() {
|
||||
NotificationCenter.default.post(name: .UnreadCountDidInitialize, object: self, userInfo: nil)
|
||||
}
|
||||
|
||||
func postUnreadCountDidChangeNotification() {
|
||||
@MainActor func postUnreadCountDidChangeNotification() {
|
||||
NotificationCenter.default.post(name: .UnreadCountDidChange, object: self, userInfo: nil)
|
||||
}
|
||||
|
||||
func calculateUnreadCount<T: Collection>(_ children: T) -> Int {
|
||||
@MainActor func calculateUnreadCount<T: Collection>(_ children: T) -> Int {
|
||||
let updatedUnreadCount = children.reduce(0) { (result, oneChild) -> Int in
|
||||
if let oneUnreadCountProvider = oneChild as? UnreadCountProvider {
|
||||
return result + oneUnreadCountProvider.unreadCount
|
||||
|
|
|
@ -13,8 +13,9 @@ import Articles
|
|||
import Core
|
||||
|
||||
protocol DetailWebViewControllerDelegate: AnyObject {
|
||||
func mouseDidEnter(_: DetailWebViewController, link: String)
|
||||
func mouseDidExit(_: DetailWebViewController)
|
||||
|
||||
@MainActor func mouseDidEnter(_: DetailWebViewController, link: String)
|
||||
@MainActor func mouseDidExit(_: DetailWebViewController)
|
||||
}
|
||||
|
||||
final class DetailWebViewController: NSViewController {
|
||||
|
|
|
@ -13,7 +13,8 @@ import Core
|
|||
|
||||
// MARK: - AccountsPreferencesAddAccountDelegate
|
||||
protocol AccountsPreferencesAddAccountDelegate {
|
||||
func presentSheetForAccount(_ accountType: AccountType)
|
||||
|
||||
@MainActor func presentSheetForAccount(_ accountType: AccountType)
|
||||
}
|
||||
|
||||
// MARK: - AccountsPreferencesViewController
|
||||
|
|
|
@ -15,7 +15,7 @@ public extension Notification.Name {
|
|||
|
||||
final class ArticleThemesManager: NSObject, NSFilePresenter {
|
||||
|
||||
static var shared: ArticleThemesManager!
|
||||
@MainActor static var shared: ArticleThemesManager!
|
||||
public let folderPath: String
|
||||
|
||||
lazy var presentedItemOperationQueue = OperationQueue.main
|
||||
|
|
Loading…
Reference in New Issue