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)") + } } }