Prevent sync errors from crashing the app

This commit is contained in:
Shadowfacts 2022-01-12 13:56:03 -05:00
parent 9deafa4b33
commit be788bd0a6
4 changed files with 58 additions and 15 deletions

View File

@ -19,6 +19,7 @@ extension Item {
@NSManaged public var author: String? @NSManaged public var author: String?
@NSManaged public var content: String? @NSManaged public var content: String?
@NSManaged public var id: String? @NSManaged public var id: String?
@NSManaged public var needsReadStateSync: Bool
@NSManaged public var published: Date? @NSManaged public var published: Date?
@NSManaged public var read: Bool @NSManaged public var read: Bool
@NSManaged public var title: String? @NSManaged public var title: String?

View File

@ -17,6 +17,7 @@
<attribute name="author" optional="YES" attributeType="String"/> <attribute name="author" optional="YES" attributeType="String"/>
<attribute name="content" optional="YES" attributeType="String"/> <attribute name="content" optional="YES" attributeType="String"/>
<attribute name="id" attributeType="String"/> <attribute name="id" attributeType="String"/>
<attribute name="needsReadStateSync" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES"/>
<attribute name="published" optional="YES" attributeType="Date" usesScalarValueType="NO"/> <attribute name="published" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="read" attributeType="Boolean" usesScalarValueType="YES"/> <attribute name="read" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="title" optional="YES" attributeType="String"/> <attribute name="title" optional="YES" attributeType="String"/>
@ -29,7 +30,7 @@
<elements> <elements>
<element name="Feed" positionX="-54" positionY="9" width="128" height="119"/> <element name="Feed" positionX="-54" positionY="9" width="128" height="119"/>
<element name="Group" positionX="-63" positionY="-18" width="128" height="74"/> <element name="Group" positionX="-63" positionY="-18" width="128" height="74"/>
<element name="Item" positionX="-45" positionY="63" width="128" height="149"/> <element name="Item" positionX="-45" positionY="63" width="128" height="164"/>
<element name="SyncState" positionX="-63" positionY="90" width="128" height="44"/> <element name="SyncState" positionX="-63" positionY="90" width="128" height="44"/>
</elements> </elements>
</model> </model>

View File

@ -55,29 +55,59 @@ class FervorController {
accessToken = token.accessToken accessToken = token.accessToken
} }
func syncAll() async { func syncAll() async throws {
logger.info("Syncing groups and feeds") logger.info("Syncing groups and feeds")
async let groups = try! client.groups() async let groups = try client.groups()
async let feeds = try! client.feeds() async let feeds = try client.feeds()
try! await persistentContainer.sync(serverGroups: groups, serverFeeds: feeds) try await persistentContainer.sync(serverGroups: groups, serverFeeds: feeds)
let lastSync = try! await persistentContainer.lastSyncDate() let lastSync = try await persistentContainer.lastSyncDate()
logger.info("Syncing items with last sync date: \(String(describing: lastSync), privacy: .public)") logger.info("Syncing items with last sync date: \(String(describing: lastSync), privacy: .public)")
let update = try! await client.syncItems(lastSync: lastSync) let update = try await client.syncItems(lastSync: lastSync)
try! await persistentContainer.syncItems(update) try await persistentContainer.syncItems(update)
try! await persistentContainer.updateLastSyncDate(update.syncTimestamp) try await persistentContainer.updateLastSyncDate(update.syncTimestamp)
} }
@MainActor @MainActor
func syncReadToServer() async throws { func syncReadToServer() async throws {
var count = 0 var count = 0
for case let item as Item in self.persistentContainer.viewContext.updatedObjects { // todo: there should be a batch update api endpoint
count += 1 for case let item as Item in persistentContainer.viewContext.updatedObjects {
let f = item.read ? client.read(item:) : client.unread(item:) let f = item.read ? client.read(item:) : client.unread(item:)
let _ = try await f(item.id!) 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") logger.info("Synced \(count, privacy: .public) read/unread to server")
try self.persistentContainer.viewContext.save()
do {
try persistentContainer.viewContext.save()
} catch {
logger.error("Failed to save view context: \(error.localizedDescription, privacy: .public)")
}
} }
} }

View File

@ -6,12 +6,15 @@
// //
import UIKit import UIKit
import OSLog
class SceneDelegate: UIResponder, UIWindowSceneDelegate { class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow? var window: UIWindow?
private(set) var fervorController: FervorController! private(set) var fervorController: FervorController!
private let logger = Logger(subsystem: Bundle.main.bundleIdentifier!, category: "SceneDelegate")
func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) { func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
// Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`. // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
@ -51,7 +54,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
// This may occur due to temporary interruptions (ex. an incoming phone call). // This may occur due to temporary interruptions (ex. an incoming phone call).
Task(priority: .userInitiated) { Task(priority: .userInitiated) {
try await self.fervorController?.syncReadToServer() do {
try await self.fervorController?.syncReadToServer()
} catch {
logger.error("Unable to sync read state to server: \(error.localizedDescription, privacy: .public)")
}
} }
} }
@ -74,7 +81,11 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
window!.rootViewController = nav window!.rootViewController = nav
Task(priority: .userInitiated) { Task(priority: .userInitiated) {
await self.fervorController.syncAll() do {
try await self.fervorController.syncAll()
} catch {
logger.error("Unable to sync from server: \(error.localizedDescription, privacy: .public)")
}
} }
} }