Make BatchUpdate MainActor.

This commit is contained in:
Brent Simmons 2024-03-23 16:26:10 -07:00
parent 177d660cff
commit e867487031
8 changed files with 125 additions and 113 deletions

View File

@ -675,22 +675,24 @@ private extension FeedbinAccountDelegate {
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
self.forceExpireFolderFeedRelationship(account, tags) self.forceExpireFolderFeedRelationship(account, tags)
self.caller.retrieveTaggings { result in self.caller.retrieveTaggings { result in
switch result {
case .success(let taggings):
BatchUpdate.shared.perform {
self.syncFolders(account, tags)
self.syncFeeds(account, subscriptions)
self.syncFeedFolderRelationship(account, taggings)
}
self.refreshProgress.completeTask() MainActor.assumeIsolated {
completion(.success(())) switch result {
case .success(let taggings):
case .failure(let error):
completion(.failure(error)) BatchUpdate.shared.perform {
self.syncFolders(account, tags)
self.syncFeeds(account, subscriptions)
self.syncFeedFolderRelationship(account, taggings)
}
self.refreshProgress.completeTask()
completion(.success(()))
case .failure(let error):
completion(.failure(error))
}
} }
} }
case .failure(let error): case .failure(let error):

View File

@ -81,45 +81,47 @@ final class LocalAccountDelegate: AccountDelegate {
} }
func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) { func importOPML(for account:Account, opmlFile: URL, completion: @escaping (Result<Void, Error>) -> Void) {
var fileData: Data?
Task { @MainActor in
do { var fileData: Data?
fileData = try Data(contentsOf: opmlFile)
} catch { do {
completion(.failure(error)) fileData = try Data(contentsOf: opmlFile)
return } catch {
} completion(.failure(error))
return
guard let opmlData = fileData else { }
guard let opmlData = fileData else {
completion(.success(()))
return
}
let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData)
var opmlDocument: RSOPMLDocument?
do {
opmlDocument = try RSOPMLParser.parseOPML(with: parserData)
} catch {
completion(.failure(error))
return
}
guard let loadDocument = opmlDocument else {
completion(.success(()))
return
}
guard let children = loadDocument.children else {
return
}
BatchUpdate.shared.perform {
account.loadOPMLItems(children)
}
completion(.success(())) completion(.success(()))
return
} }
let parserData = ParserData(url: opmlFile.absoluteString, data: opmlData)
var opmlDocument: RSOPMLDocument?
do {
opmlDocument = try RSOPMLParser.parseOPML(with: parserData)
} catch {
completion(.failure(error))
return
}
guard let loadDocument = opmlDocument else {
completion(.success(()))
return
}
guard let children = loadDocument.children else {
return
}
BatchUpdate.shared.perform {
account.loadOPMLItems(children)
}
completion(.success(()))
} }
func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) { func createFeed(for account: Account, url urlString: String, name: String?, container: Container, validateFeed: Bool, completion: @escaping (Result<Feed, Error>) -> Void) {
@ -233,57 +235,60 @@ private extension LocalAccountDelegate {
func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) { func createRSSFeed(for account: Account, url: URL, editedName: String?, container: Container, completion: @escaping (Result<Feed, Error>) -> Void) {
// We need to use a batch update here because we need to assign add the feed to the Task { @MainActor in
// container before the name has been downloaded. This will put it in the sidebar // We need to use a batch update here because we need to assign add the feed to the
// with an Untitled name if we don't delay it being added to the sidebar. // container before the name has been downloaded. This will put it in the sidebar
BatchUpdate.shared.start() // with an Untitled name if we don't delay it being added to the sidebar.
refreshProgress.addToNumberOfTasksAndRemaining(1) BatchUpdate.shared.start()
FeedFinder.find(url: url) { result in refreshProgress.addToNumberOfTasksAndRemaining(1)
FeedFinder.find(url: url) { result in
switch result {
case .success(let feedSpecifiers):
guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers),
let url = URL(string: bestFeedSpecifier.urlString) else {
self.refreshProgress.completeTask()
BatchUpdate.shared.end()
completion(.failure(AccountError.createErrorNotFound))
return
}
if account.hasFeed(withURL: bestFeedSpecifier.urlString) { MainActor.assumeIsolated {
self.refreshProgress.completeTask() switch result {
BatchUpdate.shared.end() case .success(let feedSpecifiers):
completion(.failure(AccountError.createErrorAlreadySubscribed)) guard let bestFeedSpecifier = FeedSpecifier.bestFeed(in: feedSpecifiers),
return let url = URL(string: bestFeedSpecifier.urlString) else {
} self.refreshProgress.completeTask()
InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask()
if let parsedFeed = parsedFeed {
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
feed.editedName = editedName
container.addFeed(feed)
account.update(feed, with: parsedFeed, {_ in
BatchUpdate.shared.end() BatchUpdate.shared.end()
completion(.success(feed)) completion(.failure(AccountError.createErrorNotFound))
}) return
} else { }
if account.hasFeed(withURL: bestFeedSpecifier.urlString) {
self.refreshProgress.completeTask()
BatchUpdate.shared.end()
completion(.failure(AccountError.createErrorAlreadySubscribed))
return
}
InitialFeedDownloader.download(url) { parsedFeed in
self.refreshProgress.completeTask()
if let parsedFeed = parsedFeed {
let feed = account.createFeed(with: nil, url: url.absoluteString, feedID: url.absoluteString, homePageURL: nil)
feed.editedName = editedName
container.addFeed(feed)
account.update(feed, with: parsedFeed, {_ in
MainActor.assumeIsolated {
BatchUpdate.shared.end()
completion(.success(feed))
}
})
} else {
BatchUpdate.shared.end()
completion(.failure(AccountError.createErrorNotFound))
}
}
case .failure:
BatchUpdate.shared.end() BatchUpdate.shared.end()
self.refreshProgress.completeTask()
completion(.failure(AccountError.createErrorNotFound)) completion(.failure(AccountError.createErrorNotFound))
} }
} }
case .failure:
BatchUpdate.shared.end()
self.refreshProgress.completeTask()
completion(.failure(AccountError.createErrorNotFound))
} }
} }
} }
} }

View File

@ -21,16 +21,19 @@ extension NewsBlurAccountDelegate {
os_log(.debug, log: log, "Refreshing feeds...") os_log(.debug, log: log, "Refreshing feeds...")
caller.retrieveFeeds { result in caller.retrieveFeeds { result in
switch result {
case .success((let feeds, let folders)): MainActor.assumeIsolated {
BatchUpdate.shared.perform { switch result {
self.syncFolders(account, folders) case .success((let feeds, let folders)):
self.syncFeeds(account, feeds) BatchUpdate.shared.perform {
self.syncFeedFolderRelationship(account, folders) self.syncFolders(account, folders)
self.syncFeeds(account, feeds)
self.syncFeedFolderRelationship(account, folders)
}
completion(.success(()))
case .failure(let error):
completion(.failure(error))
} }
completion(.success(()))
case .failure(let error):
completion(.failure(error))
} }
} }
} }

View File

@ -689,10 +689,12 @@ private extension ReaderAPIAccountDelegate {
self.refreshProgress.completeTask() self.refreshProgress.completeTask()
switch result { switch result {
case .success(let subscriptions): case .success(let subscriptions):
BatchUpdate.shared.perform { MainActor.assumeIsolated {
self.syncFolders(account, tags) BatchUpdate.shared.perform {
self.syncFeeds(account, subscriptions) self.syncFolders(account, tags)
self.syncFeedFolderRelationship(account, subscriptions) self.syncFeeds(account, subscriptions)
self.syncFeedFolderRelationship(account, subscriptions)
}
} }
completion(.success(())) completion(.success(()))
case .failure(let error): case .failure(let error):

View File

@ -19,10 +19,10 @@ public extension Notification.Name {
} }
/// A class for batch updating. /// A class for batch updating.
public final class BatchUpdate { @MainActor public final class BatchUpdate {
/// The shared batch update object. /// The shared batch update object.
public static let shared = BatchUpdate() @MainActor public static let shared = BatchUpdate()
private var count = 0 private var count = 0

View File

@ -67,7 +67,7 @@ class ScriptableAccount: NSObject, UniqueIdScriptingObject, ScriptingObjectConta
return self.classDescription as! NSScriptClassDescription return self.classDescription as! NSScriptClassDescription
} }
func deleteElement(_ element:ScriptingObject) { @MainActor func deleteElement(_ element:ScriptingObject) {
if let scriptableFolder = element as? ScriptableFolder { if let scriptableFolder = element as? ScriptableFolder {
BatchUpdate.shared.perform { BatchUpdate.shared.perform {
account.removeFolder(scriptableFolder.folder) { result in account.removeFolder(scriptableFolder.folder) { result in

View File

@ -50,7 +50,7 @@ class ScriptableFolder: NSObject, UniqueIdScriptingObject, ScriptingObjectContai
return self.classDescription as! NSScriptClassDescription return self.classDescription as! NSScriptClassDescription
} }
func deleteElement(_ element:ScriptingObject) { @MainActor func deleteElement(_ element:ScriptingObject) {
if let scriptableFeed = element as? ScriptableFeed { if let scriptableFeed = element as? ScriptableFeed {
BatchUpdate.shared.perform { BatchUpdate.shared.perform {
folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in } folder.account?.removeFeed(scriptableFeed.feed, from: folder) { result in }

View File

@ -15,7 +15,7 @@ extension UIView: MarkAsReadAlertControllerSourceType {}
extension UIBarButtonItem: MarkAsReadAlertControllerSourceType {} extension UIBarButtonItem: MarkAsReadAlertControllerSourceType {}
struct MarkAsReadAlertController { @MainActor struct MarkAsReadAlertController {
static func confirm<T>(_ controller: UIViewController?, static func confirm<T>(_ controller: UIViewController?,
coordinator: SceneCoordinator?, coordinator: SceneCoordinator?,