Prevent sync errors from crashing the app
This commit is contained in:
parent
9deafa4b33
commit
be788bd0a6
|
@ -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?
|
||||||
|
|
|
@ -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>
|
|
@ -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:)
|
||||||
|
do {
|
||||||
let _ = try await f(item.id!)
|
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)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
import OSLog
|
||||||
|
|
||||||
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
|
@ -13,6 +14,8 @@ class SceneDelegate: UIResponder, UIWindowSceneDelegate {
|
||||||
|
|
||||||
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`.
|
||||||
// If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
|
// If using a storyboard, the `window` property will automatically be initialized and attached to the 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) {
|
||||||
|
do {
|
||||||
try await self.fervorController?.syncReadToServer()
|
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)")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue