diff --git a/Reader/CoreData/Item+CoreDataProperties.swift b/Reader/CoreData/Item+CoreDataProperties.swift
index c8fad1b..16c93ab 100644
--- a/Reader/CoreData/Item+CoreDataProperties.swift
+++ b/Reader/CoreData/Item+CoreDataProperties.swift
@@ -19,6 +19,7 @@ extension Item {
@NSManaged public var author: String?
@NSManaged public var content: String?
@NSManaged public var id: String?
+ @NSManaged public var needsReadStateSync: Bool
@NSManaged public var published: Date?
@NSManaged public var read: Bool
@NSManaged public var title: String?
diff --git a/Reader/CoreData/Reader.xcdatamodeld/Reader.xcdatamodel/contents b/Reader/CoreData/Reader.xcdatamodeld/Reader.xcdatamodel/contents
index 2a5b062..280cb74 100644
--- a/Reader/CoreData/Reader.xcdatamodeld/Reader.xcdatamodel/contents
+++ b/Reader/CoreData/Reader.xcdatamodeld/Reader.xcdatamodel/contents
@@ -17,6 +17,7 @@
+
@@ -29,7 +30,7 @@
-
+
\ No newline at end of file
diff --git a/Reader/FervorController.swift b/Reader/FervorController.swift
index 3e14c68..94689bc 100644
--- a/Reader/FervorController.swift
+++ b/Reader/FervorController.swift
@@ -55,29 +55,59 @@ class FervorController {
accessToken = token.accessToken
}
- func syncAll() async {
+ 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)
+ 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()
+ 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)
+ 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
- for case let item as Item in self.persistentContainer.viewContext.updatedObjects {
- count += 1
+ // 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:)
- 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")
- try self.persistentContainer.viewContext.save()
+
+ do {
+ try persistentContainer.viewContext.save()
+ } catch {
+ logger.error("Failed to save view context: \(error.localizedDescription, privacy: .public)")
+ }
}
}
diff --git a/Reader/SceneDelegate.swift b/Reader/SceneDelegate.swift
index c6adc12..7b1d60c 100644
--- a/Reader/SceneDelegate.swift
+++ b/Reader/SceneDelegate.swift
@@ -6,12 +6,15 @@
//
import UIKit
+import OSLog
class SceneDelegate: UIResponder, UIWindowSceneDelegate {
var window: UIWindow?
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) {
// 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).
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
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)")
+ }
}
}