diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/Highlight.swift b/apple/OmnivoreKit/Sources/Models/DataModels/Highlight.swift index 0f78f36ef..182f71364 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/Highlight.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/Highlight.swift @@ -2,9 +2,7 @@ import CoreData import Foundation public extension Highlight { - var unwrappedID: String { - id ?? "" - } + var unwrappedID: String { id ?? "" } static func lookup(byID highlightID: String, inContext context: NSManagedObjectContext) -> Highlight? { let fetchRequest: NSFetchRequest = Highlight.fetchRequest() diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Errors/ServerError.swift b/apple/OmnivoreKit/Sources/Services/DataService/Errors/ServerError.swift index f71a87f59..c3781974f 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Errors/ServerError.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Errors/ServerError.swift @@ -1,6 +1,10 @@ import Foundation import Models +public enum CoreDataError: Error { + case general +} + public enum ServerError: String, Error { case noConnection case unauthenticated diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift index d5a0ea97d..5b3c9d5f8 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift @@ -4,7 +4,7 @@ import Models import SwiftGraphQL extension DataService { - // swiftlint:disable:next function_parameter_count function_body_length + // swiftlint:disable:next function_parameter_count public func mergeHighlights( shortId: String, highlightID: String, diff --git a/apple/OmnivoreKit/Sources/Services/DataService/OfflineSync.swift b/apple/OmnivoreKit/Sources/Services/DataService/OfflineSync.swift new file mode 100644 index 000000000..ea89ccce3 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/DataService/OfflineSync.swift @@ -0,0 +1,85 @@ +import CoreData +import Foundation +import Models + +extension DataService { + func syncOfflineItemsWithServerIfNeeded() async throws { + // TODO: send a simple request to see if we're online? + var unsyncedLinkedItems = [LinkedItem]() + var unsyncedHighlights = [Highlight]() + + // LinkedItems + let itemsFetchRequest: NSFetchRequest = LinkedItem.fetchRequest() + itemsFetchRequest.predicate = NSPredicate( + format: "serverSyncStatus != %@", ServerSyncStatus.isNSync.rawValue + ) + + // Highlights + let highlightsFetchRequest: NSFetchRequest = Highlight.fetchRequest() + highlightsFetchRequest.predicate = NSPredicate( + format: "serverSyncStatus != %@", ServerSyncStatus.isNSync.rawValue + ) + + try await backgroundContext.perform(schedule: .immediate) { [weak self] in + guard let self = self else { return } + + do { + unsyncedLinkedItems = try itemsFetchRequest.execute() + unsyncedHighlights = try highlightsFetchRequest.execute() + } catch { + throw CoreDataError.general + } + + self.syncLinkedItems(unsyncedLinkedItems: unsyncedLinkedItems) + self.syncHighlights(unsyncedHighlights: unsyncedHighlights) + } + } + + private func syncLinkedItems(unsyncedLinkedItems: [LinkedItem]) { + for item in unsyncedLinkedItems { + guard let syncStatus = ServerSyncStatus(rawValue: Int(item.serverSyncStatus)) else { continue } + + switch syncStatus { + case .isNSync, .isSyncing, .needsCreation: + break + case .needsDeletion: + item.serverSyncStatus = Int64(ServerSyncStatus.isSyncing.rawValue) + syncLinkDeletion(itemID: item.unwrappedID, objectID: item.objectID) + case .needsUpdate: + item.serverSyncStatus = Int64(ServerSyncStatus.isSyncing.rawValue) + syncLinkArchiveStatus(itemID: item.unwrappedID, objectID: item.objectID, archived: item.isArchived) + } + } + } + + private func syncHighlights(unsyncedHighlights: [Highlight]) { + for highlight in unsyncedHighlights { + guard let syncStatus = ServerSyncStatus(rawValue: Int(highlight.serverSyncStatus)) else { continue } + + switch syncStatus { + case .isNSync, .isSyncing: + break + case .needsCreation: + highlight.serverSyncStatus = Int64(ServerSyncStatus.isSyncing.rawValue) + syncHighlightCreation( + highlight: InternalHighlight.make(from: highlight), + articleId: highlight.linkedItem?.unwrappedID ?? "" + ) + case .needsDeletion: + highlight.serverSyncStatus = Int64(ServerSyncStatus.isSyncing.rawValue) + syncHighlightDeletion(highlightID: highlight.unwrappedID, objectID: highlight.objectID) + case .needsUpdate: + if let annotation = highlight.annotation { + highlight.serverSyncStatus = Int64(ServerSyncStatus.isSyncing.rawValue) + syncHighlightAttributes( + highlightID: highlight.unwrappedID, + objectID: highlight.objectID, + annotation: annotation + ) + } else { + highlight.serverSyncStatus = Int64(ServerSyncStatus.isNSync.rawValue) + } + } + } + } +}