frenzy-ios/Reader/FervorController.swift

135 lines
4.9 KiB
Swift

//
// FervorController.swift
// Reader
//
// Created by Shadowfacts on 11/25/21.
//
import Foundation
import Fervor
import OSLog
class FervorController {
static let oauthRedirectURI = URL(string: "frenzy://oauth-callback")!
let instanceURL: URL
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "FervorController")
let client: FervorClient
private(set) var account: LocalData.Account?
private(set) var clientID: String?
private(set) var clientSecret: String?
private(set) var accessToken: String?
private(set) var persistentContainer: PersistentContainer!
init(instanceURL: URL) {
self.instanceURL = instanceURL
self.client = FervorClient(instanceURL: instanceURL, accessToken: nil)
}
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)
}
func register() async throws -> ClientRegistration {
let registration = try await client.register(clientName: "Frenzy iOS", website: nil, redirectURI: FervorController.oauthRedirectURI)
clientID = registration.clientID
clientSecret = registration.clientSecret
return registration
}
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 {
logger.info("Syncing groups and feeds")
async let groups = try client.groups()
async let feeds = try client.feeds()
try await persistentContainer.sync(serverGroups: groups, serverFeeds: feeds)
let lastSync = try await persistentContainer.lastSyncDate()
logger.info("Syncing items with last sync date: \(String(describing: lastSync), privacy: .public)")
let update = try await client.syncItems(lastSync: lastSync)
try await persistentContainer.syncItems(update)
try await persistentContainer.updateLastSyncDate(update.syncTimestamp)
}
@MainActor
func syncReadToServer() async throws {
var count = 0
// todo: there should be a batch update api endpoint
for case let item as Item in persistentContainer.viewContext.updatedObjects {
let f = item.read ? client.read(item:) : client.unread(item:)
do {
let _ = try await f(item.id!)
count += 1
} catch {
logger.error("Failed to sync read state: \(error.localizedDescription, privacy: .public)")
item.needsReadStateSync = true
}
}
// try to sync items which failed last time
let req = Item.fetchRequest()
req.predicate = NSPredicate(format: "needsReadStateSync = YES")
if let needsSync = try? persistentContainer.viewContext.fetch(req) {
for item in needsSync {
let f = item.read ? client.read(item:) : client.unread(item:)
do {
let _ = try await f(item.id!)
count += 1
item.needsReadStateSync = false
} catch {
logger.error("Failed to sync read state again: \(error.localizedDescription, privacy: .public)")
item.needsReadStateSync = true
// todo: this should probably fail after a certain number of attempts
}
}
}
logger.info("Synced \(count, privacy: .public) read/unread to server")
do {
try persistentContainer.viewContext.save()
} catch {
logger.error("Failed to save view context: \(error.localizedDescription, privacy: .public)")
}
}
@MainActor
func markItem(_ item: Item, read: Bool) async {
item.read = read
do {
let f = item.read ? client.read(item:) : client.unread(item:)
_ = try await f(item.id!)
item.needsReadStateSync = false
} catch {
logger.error("Failed to mark item (un)read: \(error.localizedDescription, privacy: .public)")
item.needsReadStateSync = true
}
if persistentContainer.viewContext.hasChanges {
do {
try persistentContainer.viewContext.save()
} catch {
logger.error("Failed to save view context: \(error.localizedDescription, privacy: .public)")
}
}
}
}