mirror of
https://github.com/Ranchero-Software/NetNewsWire.git
synced 2024-12-22 23:58:36 +01:00
Fix numerous concurrency warnings.
This commit is contained in:
parent
acd86c9e2a
commit
177d660cff
@ -209,15 +209,15 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
let dataFolder: String
|
||||
let database: ArticlesDatabase
|
||||
var delegate: AccountDelegate
|
||||
static let saveQueue = CoalescingQueue(name: "Account Save Queue", interval: 1.0)
|
||||
@MainActor static let saveQueue = CoalescingQueue(name: "Account Save Queue", interval: 1.0)
|
||||
|
||||
private var unreadCounts = [String: Int]() // [feedID: Int]
|
||||
|
||||
private var _flattenedFeeds = Set<Feed>()
|
||||
private var flattenedFeedsNeedUpdate = true
|
||||
|
||||
private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
|
||||
private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.plist"), account: self)
|
||||
@MainActor private lazy var opmlFile = OPMLFile(filename: (dataFolder as NSString).appendingPathComponent("Subscriptions.opml"), account: self)
|
||||
@MainActor private lazy var metadataFile = AccountMetadataFile(filename: (dataFolder as NSString).appendingPathComponent("Settings.plist"), account: self)
|
||||
var metadata = AccountMetadata() {
|
||||
didSet {
|
||||
delegate.accountMetadata = metadata
|
||||
@ -248,7 +248,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
else {
|
||||
NotificationCenter.default.post(name: .AccountRefreshDidFinish, object: self)
|
||||
opmlFile.markAsDirty()
|
||||
Task { @MainActor in
|
||||
opmlFile.markAsDirty()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -258,7 +260,7 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
return delegate.refreshProgress
|
||||
}
|
||||
|
||||
init(dataFolder: String, type: AccountType, accountID: String, secretsProvider: SecretsProvider, transport: Transport? = nil) {
|
||||
@MainActor init(dataFolder: String, type: AccountType, accountID: String, secretsProvider: SecretsProvider, transport: Transport? = nil) {
|
||||
switch type {
|
||||
case .onMyMac:
|
||||
self.delegate = LocalAccountDelegate()
|
||||
@ -480,9 +482,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
}
|
||||
|
||||
public func save() {
|
||||
metadataFile.save()
|
||||
feedMetadataFile.save()
|
||||
opmlFile.save()
|
||||
Task { @MainActor in
|
||||
metadataFile.save()
|
||||
feedMetadataFile.save()
|
||||
opmlFile.save()
|
||||
}
|
||||
}
|
||||
|
||||
public func prepareForDeletion() {
|
||||
@ -775,9 +779,11 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
public func structureDidChange() {
|
||||
// Feeds were added or deleted. Or folders added or deleted.
|
||||
// Or feeds inside folders were added or deleted.
|
||||
opmlFile.markAsDirty()
|
||||
flattenedFeedsNeedUpdate = true
|
||||
feedDictionariesNeedUpdate = true
|
||||
Task { @MainActor in
|
||||
opmlFile.markAsDirty()
|
||||
flattenedFeedsNeedUpdate = true
|
||||
feedDictionariesNeedUpdate = true
|
||||
}
|
||||
}
|
||||
|
||||
func update(_ feed: Feed, with parsedFeed: ParsedFeed, _ completion: @escaping UpdateArticlesCompletionBlock) {
|
||||
@ -1059,7 +1065,9 @@ public final class Account: DisplayNameProvider, UnreadCountProvider, Container,
|
||||
|
||||
extension Account: AccountMetadataDelegate {
|
||||
func valueDidChange(_ accountMetadata: AccountMetadata, key: AccountMetadata.CodingKeys) {
|
||||
metadataFile.markAsDirty()
|
||||
Task { @MainActor in
|
||||
metadataFile.markAsDirty()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1068,11 +1076,14 @@ extension Account: AccountMetadataDelegate {
|
||||
extension Account: FeedMetadataDelegate {
|
||||
|
||||
func valueDidChange(_ feedMetadata: FeedMetadata, key: FeedMetadata.CodingKeys) {
|
||||
feedMetadataFile.markAsDirty()
|
||||
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
|
||||
return
|
||||
|
||||
Task { @MainActor in
|
||||
feedMetadataFile.markAsDirty()
|
||||
guard let feed = existingFeed(withFeedID: feedMetadata.feedID) else {
|
||||
return
|
||||
}
|
||||
feed.postFeedSettingDidChangeNotification(key)
|
||||
}
|
||||
feed.postFeedSettingDidChangeNotification(key)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -96,8 +96,8 @@ public final class AccountManager: UnreadCountProvider {
|
||||
return CombinedRefreshProgress(downloadProgressArray: downloadProgressArray)
|
||||
}
|
||||
|
||||
public init(accountsFolder: String, secretsProvider: SecretsProvider) {
|
||||
|
||||
@MainActor public init(accountsFolder: String, secretsProvider: SecretsProvider) {
|
||||
|
||||
self.accountsFolder = accountsFolder
|
||||
self.secretsProvider = secretsProvider
|
||||
|
||||
@ -127,7 +127,7 @@ public final class AccountManager: UnreadCountProvider {
|
||||
|
||||
// MARK: - API
|
||||
|
||||
public func createAccount(type: AccountType) -> Account {
|
||||
@MainActor public func createAccount(type: AccountType) -> Account {
|
||||
let accountID = type == .cloudKit ? "iCloud" : UUID().uuidString
|
||||
let accountFolder = (accountsFolder as NSString).appendingPathComponent("\(type.rawValue)_\(accountID)")
|
||||
|
||||
@ -438,11 +438,11 @@ private extension AccountManager {
|
||||
unreadCount = calculateUnreadCount(activeAccounts)
|
||||
}
|
||||
|
||||
func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
|
||||
@MainActor func loadAccount(_ accountSpecifier: AccountSpecifier) -> Account? {
|
||||
return Account(dataFolder: accountSpecifier.folderPath, type: accountSpecifier.type, accountID: accountSpecifier.identifier, secretsProvider: secretsProvider)
|
||||
}
|
||||
|
||||
func loadAccount(_ filename: String) -> Account? {
|
||||
@MainActor func loadAccount(_ filename: String) -> Account? {
|
||||
let folderPath = (accountsFolder as NSString).appendingPathComponent(filename)
|
||||
if let accountSpecifier = AccountSpecifier(folderPath: folderPath) {
|
||||
return loadAccount(accountSpecifier)
|
||||
@ -450,7 +450,7 @@ private extension AccountManager {
|
||||
return nil
|
||||
}
|
||||
|
||||
func readAccountsFromDisk() {
|
||||
@MainActor func readAccountsFromDisk() {
|
||||
var filenames: [String]?
|
||||
|
||||
do {
|
||||
|
@ -10,8 +10,8 @@ import Foundation
|
||||
import os.log
|
||||
import Core
|
||||
|
||||
final class AccountMetadataFile {
|
||||
|
||||
@MainActor final class AccountMetadataFile {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "accountMetadataFile")
|
||||
|
||||
private let fileURL: URL
|
||||
|
@ -17,19 +17,19 @@ final class FeedMetadataFile {
|
||||
private let fileURL: URL
|
||||
private let account: Account
|
||||
|
||||
private var isDirty = false {
|
||||
@MainActor private var isDirty = false {
|
||||
didSet {
|
||||
queueSaveToDiskIfNeeded()
|
||||
}
|
||||
}
|
||||
private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5)
|
||||
@MainActor private let saveQueue = CoalescingQueue(name: "Save Queue", interval: 0.5)
|
||||
|
||||
init(filename: String, account: Account) {
|
||||
self.fileURL = URL(fileURLWithPath: filename)
|
||||
self.account = account
|
||||
}
|
||||
|
||||
func markAsDirty() {
|
||||
@MainActor func markAsDirty() {
|
||||
isDirty = true
|
||||
}
|
||||
|
||||
@ -61,11 +61,11 @@ final class FeedMetadataFile {
|
||||
|
||||
private extension FeedMetadataFile {
|
||||
|
||||
func queueSaveToDiskIfNeeded() {
|
||||
@MainActor func queueSaveToDiskIfNeeded() {
|
||||
saveQueue.add(self, #selector(saveToDiskIfNeeded))
|
||||
}
|
||||
|
||||
@objc func saveToDiskIfNeeded() {
|
||||
@MainActor @objc func saveToDiskIfNeeded() {
|
||||
if isDirty {
|
||||
isDirty = false
|
||||
save()
|
||||
|
@ -11,8 +11,8 @@ import os.log
|
||||
import RSParser
|
||||
import Core
|
||||
|
||||
final class OPMLFile {
|
||||
|
||||
@MainActor final class OPMLFile {
|
||||
|
||||
private var log = OSLog(subsystem: Bundle.main.bundleIdentifier!, category: "opmlFile")
|
||||
|
||||
private let fileURL: URL
|
||||
|
@ -30,9 +30,9 @@ struct QueueCall: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
@objc public final class CoalescingQueue: NSObject {
|
||||
@MainActor @objc public final class CoalescingQueue: NSObject {
|
||||
|
||||
public static let standard = CoalescingQueue(name: "Standard", interval: 0.05, maxInterval: 0.1)
|
||||
@MainActor public static let standard = CoalescingQueue(name: "Standard", interval: 0.05, maxInterval: 0.1)
|
||||
public let name: String
|
||||
public var isPaused = false
|
||||
private let interval: TimeInterval
|
||||
|
@ -278,11 +278,15 @@ private extension FaviconDownloader {
|
||||
}
|
||||
|
||||
func queueSaveHomePageToFaviconURLCacheIfNeeded() {
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageToFaviconURLCacheIfNeeded))
|
||||
Task { @MainActor in
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageToFaviconURLCacheIfNeeded))
|
||||
}
|
||||
}
|
||||
|
||||
func queueSaveHomePageURLsWithNoFaviconURLCacheIfNeeded() {
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageURLsWithNoFaviconURLCacheIfNeeded))
|
||||
Task { @MainActor in
|
||||
FaviconDownloader.saveQueue.add(self, #selector(saveHomePageURLsWithNoFaviconURLCacheIfNeeded))
|
||||
}
|
||||
}
|
||||
|
||||
func saveHomePageToFaviconURLCache() {
|
||||
|
@ -18,7 +18,7 @@ extension Notification.Name {
|
||||
static let FeedIconDidBecomeAvailable = Notification.Name("FeedIconDidBecomeAvailableNotification") // UserInfoKey.feed
|
||||
}
|
||||
|
||||
public final class FeedIconDownloader {
|
||||
@MainActor public final class FeedIconDownloader {
|
||||
|
||||
private static let saveQueue = CoalescingQueue(name: "Cache Save Queue", interval: 1.0)
|
||||
|
||||
@ -81,14 +81,16 @@ public final class FeedIconDownloader {
|
||||
return IconImage.appIcon
|
||||
}
|
||||
|
||||
func checkHomePageURL() {
|
||||
@MainActor func checkHomePageURL() {
|
||||
guard let homePageURL = feed.homePageURL else {
|
||||
return
|
||||
}
|
||||
icon(forHomePageURL: homePageURL, feed: feed) { (image) in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
Task { @MainActor in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -96,11 +98,13 @@ public final class FeedIconDownloader {
|
||||
func checkFeedIconURL() {
|
||||
if let iconURL = feed.iconURL {
|
||||
icon(forURL: iconURL, feed: feed) { (image) in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
} else {
|
||||
checkHomePageURL()
|
||||
Task { @MainActor in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
} else {
|
||||
checkHomePageURL()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -110,9 +114,11 @@ public final class FeedIconDownloader {
|
||||
|
||||
if let feedProviderURL = feedURLToIconURLCache[feed.url] {
|
||||
self.icon(forURL: feedProviderURL, feed: feed) { (image) in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
Task { @MainActor in
|
||||
if let image = image {
|
||||
self.postFeedIconDidBecomeAvailableNotification(feed)
|
||||
self.cache[feed] = IconImage(image)
|
||||
}
|
||||
}
|
||||
}
|
||||
return nil
|
||||
@ -153,7 +159,7 @@ public final class FeedIconDownloader {
|
||||
|
||||
private extension FeedIconDownloader {
|
||||
|
||||
func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
func icon(forHomePageURL homePageURL: String, feed: Feed, _ imageResultBlock: @Sendable @escaping (RSImage?) -> Void) {
|
||||
|
||||
if homePagesWithNoIconURLCache.contains(homePageURL) || homePagesWithUglyIcons.contains(homePageURL) {
|
||||
imageResultBlock(nil)
|
||||
@ -168,7 +174,7 @@ private extension FeedIconDownloader {
|
||||
findIconURLForHomePageURL(homePageURL, feed: feed)
|
||||
}
|
||||
|
||||
func icon(forURL url: String, feed: Feed, _ imageResultBlock: @escaping (RSImage?) -> Void) {
|
||||
func icon(forURL url: String, feed: Feed, _ imageResultBlock: @Sendable @escaping (RSImage?) -> Void) {
|
||||
waitingForFeedURLs[url] = feed
|
||||
guard let imageData = imageDownloader.image(for: url) else {
|
||||
imageResultBlock(nil)
|
||||
@ -255,15 +261,15 @@ private extension FeedIconDownloader {
|
||||
homePagesWithNoIconURLCache = Set(decoded)
|
||||
}
|
||||
|
||||
func queueSaveFeedURLToIconURLCacheIfNeeded() {
|
||||
@MainActor func queueSaveFeedURLToIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveFeedURLToIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueSaveHomePageToIconURLCacheIfNeeded() {
|
||||
@MainActor func queueSaveHomePageToIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePageToIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
func queueHomePagesWithNoIconURLCacheIfNeeded() {
|
||||
@MainActor func queueHomePagesWithNoIconURLCacheIfNeeded() {
|
||||
FeedIconDownloader.saveQueue.add(self, #selector(saveHomePagesWithNoIconURLCacheIfNeeded))
|
||||
}
|
||||
|
||||
|
@ -22,7 +22,7 @@ final class ExtensionContainersFile {
|
||||
return containerURL!.appendingPathComponent("extension_containers.plist").path
|
||||
}()
|
||||
|
||||
private var isDirty = false {
|
||||
@MainActor private var isDirty = false {
|
||||
didSet {
|
||||
queueSaveToDiskIfNeeded()
|
||||
}
|
||||
@ -65,15 +65,15 @@ final class ExtensionContainersFile {
|
||||
|
||||
private extension ExtensionContainersFile {
|
||||
|
||||
@objc func markAsDirty() {
|
||||
@MainActor @objc func markAsDirty() {
|
||||
isDirty = true
|
||||
}
|
||||
|
||||
func queueSaveToDiskIfNeeded() {
|
||||
@MainActor func queueSaveToDiskIfNeeded() {
|
||||
saveQueue.add(self, #selector(saveToDiskIfNeeded))
|
||||
}
|
||||
|
||||
@objc func saveToDiskIfNeeded() {
|
||||
@MainActor @objc func saveToDiskIfNeeded() {
|
||||
if isDirty {
|
||||
isDirty = false
|
||||
save()
|
||||
|
@ -108,7 +108,9 @@ extension SmartFeed: ArticleFetcher {
|
||||
private extension SmartFeed {
|
||||
|
||||
func queueFetchUnreadCounts() {
|
||||
CoalescingQueue.standard.add(self, #selector(fetchUnreadCounts))
|
||||
Task { @MainActor in
|
||||
CoalescingQueue.standard.add(self, #selector(fetchUnreadCounts))
|
||||
}
|
||||
}
|
||||
|
||||
func fetchUnreadCount(for account: Account) {
|
||||
|
Loading…
Reference in New Issue
Block a user