Use async/await for conversation loading

This commit is contained in:
Shadowfacts 2022-05-11 19:10:38 -04:00
parent 1a02319894
commit 21e9ca990d
3 changed files with 65 additions and 47 deletions

View File

@ -88,6 +88,16 @@ class MastodonCachePersistentStore: NSPersistentContainer {
} }
} }
@MainActor
func addOrUpdateOnViewContext(status: Status) -> StatusMO {
let statusMO = self.upsert(status: status, context: viewContext)
if viewContext.hasChanges {
try! viewContext.save()
}
statusSubject.send(status.id)
return statusMO
}
func addAll(statuses: [Status], completion: (() -> Void)? = nil) { func addAll(statuses: [Status], completion: (() -> Void)? = nil) {
backgroundContext.perform { backgroundContext.perform {
statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) } statuses.forEach { self.upsert(status: $0, context: self.backgroundContext) }
@ -99,6 +109,14 @@ class MastodonCachePersistentStore: NSPersistentContainer {
} }
} }
func addAll(statuses: [Status]) async {
return await withCheckedContinuation { continuation in
addAll(statuses: statuses) {
continuation.resume()
}
}
}
func account(for id: String, in context: NSManagedObjectContext? = nil) -> AccountMO? { func account(for id: String, in context: NSManagedObjectContext? = nil) -> AccountMO? {
let context = context ?? viewContext let context = context ?? viewContext
let request: NSFetchRequest<AccountMO> = AccountMO.fetchRequest() let request: NSFetchRequest<AccountMO> = AccountMO.fetchRequest()

View File

@ -131,41 +131,38 @@ class ConversationTableViewController: EnhancedTableViewController {
} }
navigationItem.rightBarButtonItem = visibilityBarButtonItem navigationItem.rightBarButtonItem = visibilityBarButtonItem
loadMainStatus() Task {
await loadMainStatus()
}
} }
private func loadMainStatus() { @MainActor
private func loadMainStatus() async {
guard loadingState == .unloaded else { return } guard loadingState == .unloaded else { return }
if let mainStatus = mastodonController.persistentContainer.status(for: mainStatusID) { if let mainStatus = mastodonController.persistentContainer.status(for: mainStatusID) {
self.mainStatusLoaded(mainStatus) await mainStatusLoaded(mainStatus)
} else { } else {
loadingState = .loadingMain loadingState = .loadingMain
let request = Client.getStatus(id: mainStatusID) let req = Client.getStatus(id: mainStatusID)
mastodonController.run(request) { (response) in do {
switch response { let (status, _) = try await mastodonController.run(req)
case let .success(status, _): let statusMO = mastodonController.persistentContainer.addOrUpdateOnViewContext(status: status)
let viewContext = self.mastodonController.persistentContainer.viewContext await mainStatusLoaded(statusMO)
self.mastodonController.persistentContainer.addOrUpdate(status: status, context: viewContext) { (statusMO) in } catch {
self.mainStatusLoaded(statusMO) let error = error as! Client.Error
} loadingState = .unloaded
let config = ToastConfiguration(from: error, with: "Error Loading Status", in: self) { [weak self] toast in
case let .failure(error):
DispatchQueue.main.async {
self.loadingState = .unloaded
let config = ToastConfiguration(from: error, with: "Error Loading Status", in: self) { [weak self] (toast) in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
self?.loadMainStatus() await self?.loadMainStatus()
}
self.showToast(configuration: config, animated: true)
}
} }
showToast(configuration: config, animated: true)
return
} }
} }
} }
private func mainStatusLoaded(_ mainStatus: StatusMO) { private func mainStatusLoaded(_ mainStatus: StatusMO) async {
let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState) let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState)
var snapshot = NSDiffableDataSourceSnapshot<Section, Item>() var snapshot = NSDiffableDataSourceSnapshot<Section, Item>()
@ -175,10 +172,11 @@ class ConversationTableViewController: EnhancedTableViewController {
loadingState = .loadedMain loadingState = .loadedMain
loadContext(for: mainStatus) await loadContext(for: mainStatus)
} }
private func loadContext(for mainStatus: StatusMO) { @MainActor
private func loadContext(for mainStatus: StatusMO) async {
guard loadingState == .loadedMain else { return } guard loadingState == .loadedMain else { return }
loadingState = .loadingContext loadingState = .loadingContext
@ -188,32 +186,26 @@ class ConversationTableViewController: EnhancedTableViewController {
// todo: it would be nice to cache these contexts // todo: it would be nice to cache these contexts
let request = Status.getContext(mainStatusID) let request = Status.getContext(mainStatusID)
mastodonController.run(request) { response in do {
switch response { let (context, _) = try await mastodonController.run(request)
case let .success(context, _):
let parentIDs = self.getDirectParents(inReplyTo: mainStatusInReplyToID, from: context.ancestors) let parentIDs = self.getDirectParents(inReplyTo: mainStatusInReplyToID, from: context.ancestors)
let parentStatuses = context.ancestors.filter { parentIDs.contains($0.id) } let parentStatuses = context.ancestors.filter { parentIDs.contains($0.id) }
// todo: should this really be blindly adding all the descendants? // todo: should this really be blindly adding all the descendants?
self.mastodonController.persistentContainer.addAll(statuses: parentStatuses + context.descendants) { await mastodonController.persistentContainer.addAll(statuses: parentStatuses + context.descendants)
DispatchQueue.main.async {
self.contextLoaded(mainStatus: mainStatus, context: context, parentIDs: parentIDs) self.contextLoaded(mainStatus: mainStatus, context: context, parentIDs: parentIDs)
}
}
case let .failure(error): } catch {
DispatchQueue.main.async { let error = error as! Client.Error
self.loadingState = .loadedMain self.loadingState = .loadedMain
let config = ToastConfiguration(from: error, with: "Error Loading Content", in: self) { [weak self] (toast) in let config = ToastConfiguration(from: error, with: "Error Loading Content", in: self) { [weak self] (toast) in
toast.dismissToast(animated: true) toast.dismissToast(animated: true)
self?.loadContext(for: mainStatus) await self?.loadContext(for: mainStatus)
} }
self.showToast(configuration: config, animated: true) self.showToast(configuration: config, animated: true)
} }
} }
}
}
private func contextLoaded(mainStatus: StatusMO, context: ConversationContext, parentIDs: [String]) { private func contextLoaded(mainStatus: StatusMO, context: ConversationContext, parentIDs: [String]) {
let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState) let mainStatusItem = Item.status(id: mainStatusID, state: mainStatusState)

View File

@ -57,6 +57,14 @@ extension ToastConfiguration {
viewController.present(reporter, animated: true) viewController.present(reporter, animated: true)
} }
} }
init(from error: Client.Error, with title: String, in viewController: UIViewController, retryAction: @escaping @MainActor (ToastView) async -> Void) {
self.init(from: error, with: title, in: viewController) { toast in
Task {
await retryAction(toast)
}
}
}
} }
fileprivate extension Client.Error { fileprivate extension Client.Error {