Swift concurrency stuff
i don't know if any of this is right, but it seems like it works so...
This commit is contained in:
parent
55e4966bd1
commit
dec7a6e57f
|
@ -7,7 +7,7 @@
|
|||
|
||||
import Foundation
|
||||
|
||||
public struct ClientRegistration: Decodable {
|
||||
public struct ClientRegistration: Decodable, Sendable {
|
||||
public let clientID: String
|
||||
public let clientSecret: String
|
||||
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
// Created by Shadowfacts on 10/29/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@preconcurrency import Foundation
|
||||
|
||||
public struct Feed: Decodable {
|
||||
public struct Feed: Decodable, Sendable {
|
||||
public let id: FervorID
|
||||
public let title: String
|
||||
public let url: URL?
|
||||
|
|
|
@ -5,13 +5,13 @@
|
|||
// Created by Shadowfacts on 11/25/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@preconcurrency import Foundation
|
||||
|
||||
public class FervorClient {
|
||||
public actor FervorClient: Sendable {
|
||||
|
||||
let instanceURL: URL
|
||||
let session: URLSession
|
||||
public var accessToken: String?
|
||||
private let instanceURL: URL
|
||||
private let session: URLSession
|
||||
public private(set) var accessToken: String?
|
||||
|
||||
private let decoder: JSONDecoder = {
|
||||
let d = JSONDecoder()
|
||||
|
@ -81,7 +81,9 @@ public class FervorClient {
|
|||
"client_id": clientID,
|
||||
"client_secret": clientSecret,
|
||||
])
|
||||
return try await performRequest(request)
|
||||
let result: Token = try await performRequest(request)
|
||||
self.accessToken = result.accessToken
|
||||
return result
|
||||
}
|
||||
|
||||
public func groups() async throws -> [Group] {
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
// Created by Shadowfacts on 10/29/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@preconcurrency import Foundation
|
||||
|
||||
public struct Group: Decodable {
|
||||
public struct Group: Decodable, Sendable {
|
||||
public let id: FervorID
|
||||
public let title: String
|
||||
public let feedIDs: [FervorID]
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
// Created by Shadowfacts on 10/29/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@preconcurrency import Foundation
|
||||
|
||||
public struct Instance: Decodable {
|
||||
public struct Instance: Decodable, Sendable {
|
||||
public let name: String
|
||||
public let url: URL
|
||||
public let version: String
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
// Created by Shadowfacts on 10/29/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@preconcurrency import Foundation
|
||||
|
||||
public struct Item: Decodable {
|
||||
public struct Item: Decodable, Sendable {
|
||||
public let id: FervorID
|
||||
public let feedID: FervorID
|
||||
public let title: String?
|
||||
|
|
|
@ -5,9 +5,9 @@
|
|||
// Created by Shadowfacts on 1/9/22.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@preconcurrency import Foundation
|
||||
|
||||
public struct ItemsSyncUpdate: Decodable {
|
||||
public struct ItemsSyncUpdate: Decodable, Sendable {
|
||||
|
||||
public let syncTimestamp: Date
|
||||
public let delete: [FervorID]
|
||||
|
|
|
@ -9,7 +9,8 @@ import CoreData
|
|||
import Fervor
|
||||
import OSLog
|
||||
|
||||
class PersistentContainer: NSPersistentContainer {
|
||||
// todo: is this actually sendable?
|
||||
class PersistentContainer: NSPersistentContainer, @unchecked Sendable {
|
||||
|
||||
private static let managedObjectModel: NSManagedObjectModel = {
|
||||
let url = Bundle.main.url(forResource: "Reader", withExtension: "momd")!
|
||||
|
@ -23,13 +24,11 @@ class PersistentContainer: NSPersistentContainer {
|
|||
return context
|
||||
}()
|
||||
|
||||
private weak var fervorController: FervorController?
|
||||
weak var fervorController: FervorController?
|
||||
|
||||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "PersistentContainer")
|
||||
|
||||
init(account: LocalData.Account, fervorController: FervorController) {
|
||||
self.fervorController = fervorController
|
||||
|
||||
init(account: LocalData.Account) {
|
||||
super.init(name: "\(account.id)", managedObjectModel: PersistentContainer.managedObjectModel)
|
||||
|
||||
loadPersistentStores { description, error in
|
||||
|
@ -40,7 +39,7 @@ class PersistentContainer: NSPersistentContainer {
|
|||
}
|
||||
|
||||
@MainActor
|
||||
private func saveViewContext() async throws {
|
||||
func saveViewContext() throws {
|
||||
if viewContext.hasChanges {
|
||||
try viewContext.save()
|
||||
}
|
||||
|
|
|
@ -5,12 +5,12 @@
|
|||
// Created by Shadowfacts on 11/25/21.
|
||||
//
|
||||
|
||||
import Foundation
|
||||
@preconcurrency import Foundation
|
||||
import Fervor
|
||||
import OSLog
|
||||
@preconcurrency import OSLog
|
||||
import Combine
|
||||
|
||||
class FervorController {
|
||||
actor FervorController {
|
||||
|
||||
static let oauthRedirectURI = URL(string: "frenzy://oauth-callback")!
|
||||
|
||||
|
@ -19,36 +19,40 @@ class FervorController {
|
|||
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "FervorController")
|
||||
|
||||
let client: FervorClient
|
||||
private(set) var account: LocalData.Account?
|
||||
nonisolated let account: LocalData.Account?
|
||||
private(set) var clientID: String?
|
||||
private(set) var clientSecret: String?
|
||||
private(set) var accessToken: String?
|
||||
|
||||
private(set) var persistentContainer: PersistentContainer!
|
||||
nonisolated let persistentContainer: PersistentContainer!
|
||||
|
||||
@Published private(set) var syncState = SyncState.done
|
||||
nonisolated let syncState = PassthroughSubject<SyncState, Never>()
|
||||
private var lastSyncState = SyncState.done
|
||||
private var cancellables = Set<AnyCancellable>()
|
||||
|
||||
init(instanceURL: URL) {
|
||||
init(instanceURL: URL, account: LocalData.Account?) async {
|
||||
self.instanceURL = instanceURL
|
||||
self.client = FervorClient(instanceURL: instanceURL, accessToken: nil)
|
||||
self.client = FervorClient(instanceURL: instanceURL, accessToken: account?.accessToken)
|
||||
self.account = account
|
||||
self.clientID = account?.clientID
|
||||
self.clientSecret = account?.clientSecret
|
||||
|
||||
if let account = account {
|
||||
self.persistentContainer = PersistentContainer(account: account)
|
||||
} else {
|
||||
self.persistentContainer = nil
|
||||
}
|
||||
|
||||
persistentContainer?.fervorController = self
|
||||
}
|
||||
|
||||
convenience init(account: LocalData.Account) {
|
||||
self.init(instanceURL: account.instanceURL)
|
||||
self.account = account
|
||||
self.clientID = account.clientID
|
||||
self.clientSecret = account.clientSecret
|
||||
self.accessToken = account.accessToken
|
||||
|
||||
self.client.accessToken = account.accessToken
|
||||
|
||||
self.persistentContainer = PersistentContainer(account: account, fervorController: self)
|
||||
convenience init(account: LocalData.Account) async {
|
||||
await self.init(instanceURL: account.instanceURL, account: account)
|
||||
}
|
||||
|
||||
private func setSyncState(_ state: SyncState) {
|
||||
DispatchQueue.main.async {
|
||||
self.syncState = state
|
||||
}
|
||||
lastSyncState = state
|
||||
syncState.send(state)
|
||||
}
|
||||
|
||||
func register() async throws -> ClientRegistration {
|
||||
|
@ -60,12 +64,11 @@ class FervorController {
|
|||
|
||||
func getToken(authCode: String) async throws {
|
||||
let token = try await client.token(authCode: authCode, redirectURI: FervorController.oauthRedirectURI, clientID: clientID!, clientSecret: clientSecret!)
|
||||
client.accessToken = token.accessToken
|
||||
accessToken = token.accessToken
|
||||
}
|
||||
|
||||
func syncAll() async throws {
|
||||
guard syncState == .done else {
|
||||
guard lastSyncState == .done else {
|
||||
return
|
||||
}
|
||||
// always return to .done, even if we throw and stop syncing early
|
||||
|
@ -90,7 +93,6 @@ class FervorController {
|
|||
await ExcerptGenerator.generateAll(self)
|
||||
}
|
||||
|
||||
@MainActor
|
||||
func syncReadToServer() async {
|
||||
var count = 0
|
||||
|
||||
|
@ -138,7 +140,7 @@ class FervorController {
|
|||
func markItem(_ item: Item, read: Bool) async {
|
||||
item.read = read
|
||||
do {
|
||||
let f = item.read ? client.read(item:) : client.unread(item:)
|
||||
let f = read ? client.read(item:) : client.unread(item:)
|
||||
_ = try await f(item.id!)
|
||||
item.needsReadStateSync = false
|
||||
} catch {
|
||||
|
@ -146,12 +148,10 @@ class FervorController {
|
|||
item.needsReadStateSync = true
|
||||
}
|
||||
|
||||
if persistentContainer.viewContext.hasChanges {
|
||||
do {
|
||||
try persistentContainer.viewContext.save()
|
||||
} catch {
|
||||
logger.error("Failed to save view context: \(String(describing: error), privacy: .public)")
|
||||
}
|
||||
do {
|
||||
try self.persistentContainer.saveViewContext()
|
||||
} catch {
|
||||
logger.error("Failed to save view context: \(String(describing: error), privacy: .public)")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// Created by Shadowfacts on 10/29/21.
|
||||
//
|
||||
|
||||
@preconcurrency import Foundation
|
||||
import UIKit
|
||||
import OSLog
|
||||
|
||||
|
@ -33,11 +34,17 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
window!.rootViewController = loginVC
|
||||
} else if activity?.activityType == NSUserActivity.activateAccountType,
|
||||
let account = LocalData.accounts.first(where: { $0.id.uuidString == activity!.userInfo?["accountID"] as? String }) {
|
||||
fervorController = FervorController(account: account)
|
||||
createAppUI()
|
||||
Task { @MainActor in
|
||||
fervorController = await FervorController(account: account)
|
||||
syncFromServer()
|
||||
createAppUI()
|
||||
}
|
||||
} else if let account = LocalData.mostRecentAccount() {
|
||||
fervorController = FervorController(account: account)
|
||||
createAppUI()
|
||||
Task { @MainActor in
|
||||
fervorController = await FervorController(account: account)
|
||||
syncFromServer()
|
||||
createAppUI()
|
||||
}
|
||||
} else {
|
||||
let loginVC = LoginViewController()
|
||||
loginVC.delegate = self
|
||||
|
@ -125,9 +132,9 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
func switchToAccount(_ account: LocalData.Account) {
|
||||
func switchToAccount(_ account: LocalData.Account) async {
|
||||
LocalData.mostRecentAccountID = account.id
|
||||
fervorController = FervorController(account: account)
|
||||
fervorController = await FervorController(account: account)
|
||||
createAppUI()
|
||||
syncFromServer()
|
||||
}
|
||||
|
@ -136,15 +143,17 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
|||
|
||||
extension SceneDelegate: LoginViewControllerDelegate {
|
||||
func didLogin(with controller: FervorController) {
|
||||
let account = LocalData.Account(instanceURL: controller.instanceURL, clientID: controller.clientID!, clientSecret: controller.clientSecret!, accessToken: controller.accessToken!)
|
||||
LocalData.accounts.append(account)
|
||||
LocalData.mostRecentAccountID = account.id
|
||||
fervorController = FervorController(account: account)
|
||||
Task { @MainActor in
|
||||
let account = LocalData.Account(instanceURL: controller.instanceURL, clientID: await controller.clientID!, clientSecret: await controller.clientSecret!, accessToken: await controller.accessToken!)
|
||||
LocalData.accounts.append(account)
|
||||
LocalData.mostRecentAccountID = account.id
|
||||
fervorController = await FervorController(account: account)
|
||||
|
||||
createAppUI()
|
||||
syncFromServer()
|
||||
createAppUI()
|
||||
syncFromServer()
|
||||
|
||||
UIMenuSystem.main.setNeedsRebuild()
|
||||
UIMenuSystem.main.setNeedsRebuild()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -65,7 +65,9 @@ extension AppSplitViewController: ItemsViewControllerDelegate {
|
|||
extension AppSplitViewController: HomeViewControllerDelegate {
|
||||
func switchToAccount(_ account: LocalData.Account) {
|
||||
if let delegate = view.window?.windowScene?.delegate as? SceneDelegate {
|
||||
delegate.switchToAccount(account)
|
||||
Task { @MainActor in
|
||||
await delegate.switchToAccount(account)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -93,7 +93,7 @@ class HomeViewController: UIViewController {
|
|||
feedResultsController.delegate = self
|
||||
try! feedResultsController.performFetch()
|
||||
|
||||
fervorController.$syncState
|
||||
fervorController.syncState
|
||||
.debounce(for: .milliseconds(250), scheduler: RunLoop.main, options: nil)
|
||||
.sink { [unowned self] in
|
||||
self.syncStateChanged($0)
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
// Created by Shadowfacts on 11/25/21.
|
||||
//
|
||||
|
||||
@preconcurrency import Foundation
|
||||
import UIKit
|
||||
import AuthenticationServices
|
||||
import Fervor
|
||||
|
@ -72,7 +73,7 @@ class LoginViewController: UIViewController {
|
|||
textField.isEnabled = false
|
||||
activityIndicator.startAnimating()
|
||||
|
||||
let controller = FervorController(instanceURL: components.url!)
|
||||
let controller = await FervorController(instanceURL: components.url!, account: nil)
|
||||
|
||||
let registration: ClientRegistration
|
||||
do {
|
||||
|
@ -99,16 +100,16 @@ class LoginViewController: UIViewController {
|
|||
let components = URLComponents(url: callbackURL!, resolvingAgainstBaseURL: false)
|
||||
guard let codeItem = components?.queryItems?.first(where: { $0.name == "code" }),
|
||||
let codeValue = codeItem.value else {
|
||||
DispatchQueue.main.async {
|
||||
let alert = UIAlertController(title: "Unable to retrieve authorization code", message: error?.localizedDescription ?? "Unknown Error", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
|
||||
self.present(alert, animated: true)
|
||||
Task { @MainActor in
|
||||
let alert = UIAlertController(title: "Unable to retrieve authorization code", message: error?.localizedDescription ?? "Unknown Error", preferredStyle: .alert)
|
||||
alert.addAction(UIAlertAction(title: "Ok", style: .default, handler: nil))
|
||||
self.present(alert, animated: true)
|
||||
|
||||
self.textField.isEnabled = true
|
||||
self.activityIndicator.stopAnimating()
|
||||
}
|
||||
return
|
||||
}
|
||||
self.textField.isEnabled = true
|
||||
self.activityIndicator.stopAnimating()
|
||||
}
|
||||
return
|
||||
}
|
||||
Task { @MainActor in
|
||||
do {
|
||||
try await controller.getToken(authCode: codeValue)
|
||||
|
|
Loading…
Reference in New Issue