define viewer as a nsmanagedobject

This commit is contained in:
Satindar Dhillon
2022-04-18 13:44:52 -07:00
parent 290894ed9b
commit 1d92e64801
11 changed files with 88 additions and 48 deletions

View File

@ -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: []) ?? [])

View File

@ -56,7 +56,7 @@ enum PDFProvider {
if let viewer = viewer {
createWebAppWrapperViewModel(
username: viewer.username,
username: viewer.username ?? "",
dataService: dataService,
rawAuthCookie: rawAuthCookie
)

View File

@ -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) }
)
}

View File

@ -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)
}
}

View File

@ -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

View File

@ -64,10 +64,22 @@
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Viewer" representedClassName="Viewer" syncable="YES" codeGenerationType="class">
<attribute name="name" attributeType="String"/>
<attribute name="profileImageURL" optional="YES" attributeType="String"/>
<attribute name="userID" attributeType="String"/>
<attribute name="username" attributeType="String"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="userID"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<elements>
<element name="PersistedFeedItemLabel" positionX="-36" positionY="18" width="128" height="104"/>
<element name="PersistedFeedItem" positionX="-18" positionY="63" width="128" height="284"/>
<element name="PersistedArticleContent" positionX="9" positionY="108" width="128" height="59"/>
<element name="PersistedFeedItem" positionX="-18" positionY="63" width="128" height="284"/>
<element name="PersistedFeedItemLabel" positionX="-36" positionY="18" width="128" height="104"/>
<element name="PersistedHighlight" positionX="27" positionY="225" width="128" height="209"/>
<element name="Viewer" positionX="45" positionY="234" width="128" height="89"/>
</elements>
</model>

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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<AnyCancellable>()
@ -27,6 +28,11 @@ public final class DataService: ObservableObject {
}
}
public var currentViewer: Viewer? {
let fetchRequest: NSFetchRequest<Models.Viewer> = 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 }
)

View File

@ -6,7 +6,7 @@ import SwiftGraphQL
public extension DataService {
func articlePublisher(slug: String) -> AnyPublisher<FeedItem, BasicError> {
internalViewerPublisher()
.flatMap { self.internalArticlePublisher(username: $0.username, slug: slug) }
.flatMap { self.internalArticlePublisher(username: $0.username ?? "", slug: slug) }
.receive(on: DispatchQueue.main)
.eraseToAnyPublisher()
}

View File

@ -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, Objects.User> {
Viewer(
let selection = Selection<ViewerInternal, Objects.User> {
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<Viewer, BasicError> {
let selection = Selection<Viewer, Objects.User> {
Viewer(
let selection = Selection<ViewerInternal, Objects.User> {
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
}
}
}