diff --git a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift
index d9b3c5e48..9512dc7d0 100644
--- a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift
+++ b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift
@@ -18,9 +18,9 @@ public final class PDFViewerViewModel: ObservableObject {
}
public func loadHighlights(completion onComplete: @escaping ([Highlight]) -> Void) {
- guard let viewer = services.dataService.currentViewer else { return }
+ guard let username = services.dataService.currentViewer?.username else { return }
- services.dataService.pdfHighlightsPublisher(username: viewer.username, slug: feedItem.slug).sink(
+ services.dataService.pdfHighlightsPublisher(username: username, slug: feedItem.slug).sink(
receiveCompletion: { [weak self] completion in
guard case .failure = completion else { return }
onComplete(self?.allHighlights(fetchedHighlights: []) ?? [])
diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift
index 6ae0f7ad9..4d677e53f 100644
--- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift
@@ -56,7 +56,7 @@ enum PDFProvider {
if let viewer = viewer {
createWebAppWrapperViewModel(
- username: viewer.username,
+ username: viewer.username ?? "",
dataService: dataService,
rawAuthCookie: rawAuthCookie
)
diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift
index aab2f65ab..84dd050e9 100644
--- a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift
@@ -21,8 +21,8 @@ import Views
guard let viewer = try? await dataService.fetchViewer() else { return }
profileCardData = ProfileCardData(
- name: viewer.name,
- username: viewer.username,
+ name: viewer.name ?? "",
+ username: viewer.username ?? "",
imageURL: viewer.profileImageURL.flatMap { URL(string: $0) }
)
}
diff --git a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift
index a8afdf799..9def7c1df 100644
--- a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift
@@ -64,8 +64,8 @@ public final class RootViewModel: ObservableObject {
return
}
- if let viewer = try? await services.dataService.fetchViewer() {
- let path = linkRequestPath(username: viewer.username, requestID: linkRequestID)
+ if let username = try? await services.dataService.fetchViewer().username {
+ let path = linkRequestPath(username: username, requestID: linkRequestID)
webLinkPath = SafariWebLinkPath(id: UUID(), path: path)
}
}
diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift
index 081311164..4b7b8f855 100644
--- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift
+++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift
@@ -25,14 +25,14 @@ final class WebReaderViewModel: ObservableObject {
self.slug = slug
isLoading = true
- guard let viewer = dataService.currentViewer else { return }
+ guard let username = dataService.currentViewer?.username else { return }
if let content = dataService.pageFromCache(slug: slug) {
articleContent = content
// continue to load from the web if possible
}
- dataService.articleContentPublisher(username: viewer.username, slug: slug).sink(
+ dataService.articleContentPublisher(username: username, slug: slug).sink(
receiveCompletion: { [weak self] completion in
guard case .failure = completion else { return }
self?.isLoading = false
diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents
index 449b5cd29..655f7476c 100644
--- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents
+++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents
@@ -64,10 +64,22 @@
+
+
+
+
+
+
+
+
+
+
+
-
-
+
+
+
\ No newline at end of file
diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift
index 1f42124dd..8641865ef 100644
--- a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift
+++ b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift
@@ -7,6 +7,14 @@ public class PersistentContainer: NSPersistentContainer {
public static func make() -> PersistentContainer {
let modelURL = Bundle.module.url(forResource: "CoreDataModel", withExtension: "momd")!
let model = NSManagedObjectModel(contentsOf: modelURL)!
- return PersistentContainer(name: "DataModel", managedObjectModel: model)
+ let container = PersistentContainer(name: "DataModel", managedObjectModel: model)
+
+ container.viewContext.automaticallyMergesChangesFromParent = false
+ container.viewContext.name = "viewContext"
+ container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy
+ container.viewContext.undoManager = nil
+ container.viewContext.shouldDeleteInaccessibleFaults = true
+
+ return container
}
}
diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift b/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift
index d3588df5c..85388588b 100644
--- a/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift
+++ b/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift
@@ -44,22 +44,3 @@ public extension UserProfile {
return nil
}
}
-
-public struct Viewer {
- public let userID: String
- public let username: String
- public let name: String
- public let profileImageURL: String?
-
- public init(
- username: String,
- name: String,
- profileImageURL: String?,
- userID: String
- ) {
- self.username = username
- self.name = name
- self.profileImageURL = profileImageURL
- self.userID = userID
- }
-}
diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift
index 83fa20999..a3bfe05d2 100644
--- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift
+++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift
@@ -2,14 +2,15 @@ import Combine
import CoreData
import Foundation
import Models
+import OSLog
public final class DataService: ObservableObject {
public static var registerIntercomUser: ((String) -> Void)?
public static var showIntercomMessenger: (() -> Void)?
public let appEnvironment: AppEnvironment
- public internal(set) var currentViewer: Viewer?
let networker: Networker
+ static let logger = Logger(subsystem: "app.omnivore", category: "data-service")
let persistentContainer: PersistentContainer
var subscriptions = Set()
@@ -27,6 +28,11 @@ public final class DataService: ObservableObject {
}
}
+ public var currentViewer: Viewer? {
+ let fetchRequest: NSFetchRequest = Viewer.fetchRequest()
+ return try? persistentContainer.viewContext.fetch(fetchRequest).first
+ }
+
public func clearHighlights() {
deletedHighlightsIDs.removeAll()
@@ -41,7 +47,7 @@ public final class DataService: ObservableObject {
do {
try persistentContainer.viewContext.save()
} catch {
- print("failed to delete objects")
+ DataService.logger.debug("failed to delete objects")
}
}
@@ -57,11 +63,11 @@ public final class DataService: ObservableObject {
public extension DataService {
func prefetchPages(items: [FeedItem]) {
- guard let viewer = currentViewer else { return }
+ guard let username = currentViewer?.username else { return }
for item in items {
let slug = item.slug
- articleContentPublisher(username: viewer.username, slug: slug).sink(
+ articleContentPublisher(username: username, slug: slug).sink(
receiveCompletion: { _ in },
receiveValue: { _ in }
)
diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift
index f2aaa530d..d6f31e93c 100644
--- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift
+++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift
@@ -6,7 +6,7 @@ import SwiftGraphQL
public extension DataService {
func articlePublisher(slug: String) -> AnyPublisher {
internalViewerPublisher()
- .flatMap { self.internalArticlePublisher(username: $0.username, slug: slug) }
+ .flatMap { self.internalArticlePublisher(username: $0.username ?? "", slug: slug) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}
diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift
index 509ff01a7..f526c23f3 100644
--- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift
+++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift
@@ -1,4 +1,5 @@
import Combine
+import CoreData
import Foundation
import Models
import SwiftGraphQL
@@ -6,16 +7,16 @@ import Utils
public extension DataService {
func fetchViewer() async throws -> Viewer {
- let selection = Selection {
- Viewer(
+ let selection = Selection {
+ ViewerInternal(
+ userID: try $0.id(),
username: try $0.profile(
selection: .init { try $0.username() }
),
name: try $0.name(),
profileImageURL: try $0.profile(
selection: .init { try $0.pictureUrl() }
- ),
- userID: try $0.id()
+ )
)
}
@@ -30,12 +31,16 @@ public extension DataService {
send(query, to: path, headers: headers) { [weak self] result in
switch result {
case let .success(payload):
- self?.currentViewer = payload.data
if UserDefaults.standard.string(forKey: Keys.userIdKey) == nil {
UserDefaults.standard.setValue(payload.data.userID, forKey: Keys.userIdKey)
DataService.registerIntercomUser?(payload.data.userID)
}
- continuation.resume(returning: payload.data)
+
+ if let self = self, let viewer = payload.data.persist(context: self.persistentContainer.viewContext) {
+ continuation.resume(returning: viewer)
+ } else {
+ continuation.resume(throwing: BasicError.message(messageText: "coredata error"))
+ }
case .failure:
continuation.resume(throwing: BasicError.message(messageText: "http error"))
}
@@ -47,16 +52,16 @@ public extension DataService {
extension DataService {
@available(*, deprecated, message: "use async version instead")
func internalViewerPublisher() -> AnyPublisher {
- let selection = Selection {
- Viewer(
+ let selection = Selection {
+ ViewerInternal(
+ userID: try $0.id(),
username: try $0.profile(
selection: .init { try $0.username() }
),
name: try $0.name(),
profileImageURL: try $0.profile(
selection: .init { try $0.pictureUrl() }
- ),
- userID: try $0.id()
+ )
)
}
@@ -72,8 +77,11 @@ extension DataService {
send(query, to: path, headers: headers) { result in
switch result {
case let .success(payload):
- self?.currentViewer = payload.data
- promise(.success(payload.data))
+ if let self = self, let viewer = payload.data.persist(context: self.persistentContainer.viewContext) {
+ promise(.success(viewer))
+ } else {
+ promise(.failure(.message(messageText: "coredata error")))
+ }
case .failure:
promise(.failure(.message(messageText: "http error")))
}
@@ -83,3 +91,28 @@ extension DataService {
.eraseToAnyPublisher()
}
}
+
+private struct ViewerInternal {
+ let userID: String
+ let username: String
+ let name: String
+ let profileImageURL: String?
+
+ func persist(context: NSManagedObjectContext) -> Viewer? {
+ let viewer = Viewer(context: context)
+ viewer.userID = userID
+ viewer.username = username
+ viewer.name = name
+ viewer.profileImageURL = profileImageURL
+
+ do {
+ try context.save()
+ DataService.logger.debug("Viewer saved succesfully")
+ return viewer
+ } catch {
+ context.rollback()
+ DataService.logger.debug("Failed to save Viewer: \(error.localizedDescription)")
+ return nil
+ }
+ }
+}