mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
separate linkeditem network fetches from persistence functions
This commit is contained in:
parent
01632c5317
commit
96b09fec8e
6 changed files with 89 additions and 71 deletions
|
|
@ -62,7 +62,7 @@ import Views
|
|||
Task { _ = try? await dataService.fetchViewer() }
|
||||
}
|
||||
|
||||
let queryResult = try? await dataService.fetchLinkedItems(
|
||||
let queryResult = try? await dataService.loadLinkedItems(
|
||||
limit: 10,
|
||||
searchQuery: searchQuery,
|
||||
cursor: isRefresh ? nil : cursor
|
||||
|
|
@ -81,7 +81,7 @@ import Views
|
|||
let newItems: [LinkedItem] = {
|
||||
var itemObjects = [LinkedItem]()
|
||||
dataService.viewContext.performAndWait {
|
||||
itemObjects = queryResult.items.compactMap { dataService.viewContext.object(with: $0) as? LinkedItem }
|
||||
itemObjects = queryResult.itemIDs.compactMap { dataService.viewContext.object(with: $0) as? LinkedItem }
|
||||
}
|
||||
return itemObjects
|
||||
}()
|
||||
|
|
|
|||
|
|
@ -64,11 +64,8 @@ import Utils
|
|||
return nil
|
||||
}
|
||||
|
||||
print("FETCHING", requestID, requestCount)
|
||||
|
||||
if let objectID = try? await dataService.fetchLinkedItem(username: username, itemID: requestID) {
|
||||
if let objectID = try? await dataService.loadLinkedItem(username: username, itemID: requestID) {
|
||||
if let linkedItem = dataService.viewContext.object(with: objectID) as? LinkedItem {
|
||||
print(" - FROM DATA SERVICE", linkedItem)
|
||||
return linkedItem
|
||||
} else {
|
||||
errorMessage = "Unable to fetch item."
|
||||
|
|
|
|||
|
|
@ -2,12 +2,12 @@ import CoreData
|
|||
import Foundation
|
||||
import Utils
|
||||
|
||||
public struct HomeFeedData { // TODO: rename this
|
||||
public let items: [NSManagedObjectID]
|
||||
public struct LinkedItemQueryResult {
|
||||
public let itemIDs: [NSManagedObjectID]
|
||||
public let cursor: String?
|
||||
|
||||
public init(items: [NSManagedObjectID], cursor: String?) {
|
||||
self.items = items
|
||||
public init(itemIDs: [NSManagedObjectID], cursor: String?) {
|
||||
self.itemIDs = itemIDs
|
||||
self.cursor = cursor
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
|
||||
public extension DataService {
|
||||
/// Requests `LinkedItem`s from the server and stores it in CoreData.
|
||||
/// - Parameters:
|
||||
/// - limit: max count of items
|
||||
/// - searchQuery: search terms and filters
|
||||
/// - cursor: cursor when loading batch for infinite list
|
||||
/// - Returns: `LinkedItemQueryResult` (managed object IDs and an optional cursor)
|
||||
func loadLinkedItems(
|
||||
limit: Int,
|
||||
searchQuery: String?,
|
||||
cursor: String?
|
||||
) async throws -> LinkedItemQueryResult {
|
||||
// Send offline changes to server before fetching items
|
||||
try? await syncOfflineItemsWithServerIfNeeded()
|
||||
|
||||
let fetchResult = try await fetchLinkedItems(limit: limit, searchQuery: searchQuery, cursor: cursor)
|
||||
|
||||
guard let itemIDs = fetchResult.items.persist(context: backgroundContext) else {
|
||||
throw BasicError.message(messageText: "CoreData error")
|
||||
}
|
||||
|
||||
return LinkedItemQueryResult(itemIDs: itemIDs, cursor: fetchResult.cursor)
|
||||
}
|
||||
|
||||
/// Requests a single `LinkedItem` from the server and stores it in CoreData
|
||||
/// - Parameters:
|
||||
/// - username: the Viewer's username
|
||||
/// - itemID: id of item being requested
|
||||
/// - Returns: The `NSManagedObjectID` of the `LinkedItem`
|
||||
func loadLinkedItem(username: String, itemID: String) async throws -> NSManagedObjectID {
|
||||
let item = try await fetchLinkedItem(username: username, itemID: itemID)
|
||||
|
||||
guard let persistedItemID = [item].persist(context: backgroundContext)?.first else {
|
||||
throw BasicError.message(messageText: "CoreData error")
|
||||
}
|
||||
|
||||
return persistedItemID
|
||||
}
|
||||
}
|
||||
|
|
@ -3,22 +3,25 @@ import Foundation
|
|||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
public extension DataService {
|
||||
struct InternalLinkedItemQueryResult {
|
||||
let items: [InternalLinkedItem]
|
||||
let cursor: String?
|
||||
}
|
||||
|
||||
extension DataService {
|
||||
/// Performs GraphQL request to fetch `InternalLinkedItem`s and a cursor value
|
||||
/// - Parameters:
|
||||
/// - limit: max number of items to return
|
||||
/// - searchQuery: search query used by server to narrow search
|
||||
/// - cursor: cursor to indicate batch cutoff
|
||||
/// - Returns: `InternalLinkedItemQueryResult` or a `ContentFetchError` if request fails.
|
||||
func fetchLinkedItems(
|
||||
limit: Int,
|
||||
searchQuery: String?,
|
||||
cursor: String?
|
||||
) async throws -> HomeFeedData {
|
||||
// Send offline changes to server before fetching items
|
||||
try? await syncOfflineItemsWithServerIfNeeded()
|
||||
|
||||
struct InternalHomeFeedData {
|
||||
let items: [InternalLinkedItem]
|
||||
let cursor: String?
|
||||
}
|
||||
|
||||
) async throws -> InternalLinkedItemQueryResult {
|
||||
enum QueryResult {
|
||||
case success(result: InternalHomeFeedData)
|
||||
case success(result: InternalLinkedItemQueryResult)
|
||||
case error(error: String)
|
||||
}
|
||||
|
||||
|
|
@ -29,7 +32,7 @@ public extension DataService {
|
|||
},
|
||||
articlesSuccess: .init {
|
||||
QueryResult.success(
|
||||
result: InternalHomeFeedData(
|
||||
result: InternalLinkedItemQueryResult(
|
||||
items: try $0.edges(selection: articleEdgeSelection.list),
|
||||
cursor: try $0.pageInfo(selection: Selection.PageInfo {
|
||||
try $0.endCursor()
|
||||
|
|
@ -61,27 +64,29 @@ public extension DataService {
|
|||
let headers = networker.defaultHeaders
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
send(query, to: path, headers: headers) { [weak self] queryResult in
|
||||
send(query, to: path, headers: headers) { queryResult in
|
||||
guard let payload = try? queryResult.get() else {
|
||||
continuation.resume(throwing: BasicError.message(messageText: "network error"))
|
||||
continuation.resume(throwing: ContentFetchError.network)
|
||||
return
|
||||
}
|
||||
|
||||
switch payload.data {
|
||||
case let .success(result: result):
|
||||
if let context = self?.backgroundContext, let items = result.items.persist(context: context) {
|
||||
continuation.resume(returning: HomeFeedData(items: items.map(\.objectID), cursor: result.cursor))
|
||||
} else {
|
||||
continuation.resume(throwing: BasicError.message(messageText: "CoreData error"))
|
||||
}
|
||||
case .error:
|
||||
continuation.resume(throwing: BasicError.message(messageText: "LinkedItem fetch error"))
|
||||
continuation.resume(returning: result)
|
||||
case let .error(error):
|
||||
continuation.resume(throwing: ContentFetchError.unknown(description: error.description))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchLinkedItem(username: String, itemID: String) async throws -> NSManagedObjectID {
|
||||
/// Performs GraphQL request to fetch a single `InternalLinkedItem`
|
||||
/// - Parameters:
|
||||
/// - username: the Viewer's username
|
||||
/// - itemID: id of the item being requested
|
||||
/// - Returns: Returns an `InternalLinkedItem` or throws a `ContentFetchError` if
|
||||
/// request could not be completed
|
||||
func fetchLinkedItem(username: String, itemID: String) async throws -> InternalLinkedItem {
|
||||
struct ArticleProps {
|
||||
let item: InternalLinkedItem
|
||||
}
|
||||
|
|
@ -91,41 +96,13 @@ public extension DataService {
|
|||
case error(error: String)
|
||||
}
|
||||
|
||||
let articleSelection = Selection.Article {
|
||||
InternalLinkedItem(
|
||||
id: try $0.id(),
|
||||
title: try $0.title(),
|
||||
createdAt: try $0.createdAt().value ?? Date(),
|
||||
savedAt: try $0.savedAt().value ?? Date(),
|
||||
readAt: try $0.readAt()?.value,
|
||||
updatedAt: try $0.updatedAt().value ?? Date(),
|
||||
state: try $0.state()?.rawValue ?? "SUCCEEDED",
|
||||
readingProgress: try $0.readingProgressPercent(),
|
||||
readingProgressAnchor: try $0.readingProgressAnchorIndex(),
|
||||
imageURLString: try $0.image(),
|
||||
onDeviceImageURLString: nil,
|
||||
documentDirectoryPath: nil,
|
||||
pageURLString: try $0.url(),
|
||||
descriptionText: try $0.description(),
|
||||
publisherURLString: try $0.originalArticleUrl(),
|
||||
siteName: try $0.siteName(),
|
||||
author: try $0.author(),
|
||||
publishDate: try $0.publishedAt()?.value,
|
||||
slug: try $0.slug(),
|
||||
isArchived: try $0.isArchived(),
|
||||
contentReader: try $0.contentReader().rawValue,
|
||||
originalHtml: nil,
|
||||
labels: try $0.labels(selection: feedItemLabelSelection.list.nullable) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
let selection = Selection<QueryResult, Unions.ArticleResult> {
|
||||
try $0.on(
|
||||
articleError: .init {
|
||||
QueryResult.error(error: try $0.errorCodes().description)
|
||||
},
|
||||
articleSuccess: .init {
|
||||
QueryResult.success(result: try $0.article(selection: articleSelection))
|
||||
QueryResult.success(result: try $0.article(selection: libraryArticleSelection))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
|
@ -139,20 +116,16 @@ public extension DataService {
|
|||
let headers = networker.defaultHeaders
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
send(query, to: path, headers: headers) { [weak self] queryResult in
|
||||
send(query, to: path, headers: headers) { queryResult in
|
||||
guard let payload = try? queryResult.get() else {
|
||||
continuation.resume(throwing: ContentFetchError.network)
|
||||
return
|
||||
}
|
||||
switch payload.data {
|
||||
case let .success(result: result):
|
||||
if let context = self?.backgroundContext, let item = [result].persist(context: context)?.first {
|
||||
continuation.resume(returning: item.objectID)
|
||||
} else {
|
||||
continuation.resume(throwing: BasicError.message(messageText: "CoreData error"))
|
||||
}
|
||||
case .error:
|
||||
continuation.resume(throwing: BasicError.message(messageText: "LinkedItem fetch error"))
|
||||
continuation.resume(returning: result)
|
||||
case let .error(error):
|
||||
continuation.resume(throwing: ContentFetchError.unknown(description: error.description))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -70,7 +70,7 @@ struct InternalLinkedItem {
|
|||
}
|
||||
|
||||
extension Sequence where Element == InternalLinkedItem {
|
||||
func persist(context: NSManagedObjectContext) -> [LinkedItem]? {
|
||||
func persist(context: NSManagedObjectContext) -> [NSManagedObjectID]? {
|
||||
var linkedItems: [LinkedItem]?
|
||||
context.performAndWait {
|
||||
linkedItems = map { $0.asManagedObject(inContext: context) }
|
||||
|
|
@ -83,7 +83,12 @@ extension Sequence where Element == InternalLinkedItem {
|
|||
print("Failed to save LinkedItems: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
return linkedItems
|
||||
|
||||
if let linkedItems = linkedItems {
|
||||
return linkedItems.map(\.objectID)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue