mirror of
https://github.com/omnivore-app/omnivore.git
synced 2026-03-11 08:54:26 +00:00
fetch items in background task
This commit is contained in:
parent
2eeec7cc77
commit
d6f9acca11
6 changed files with 150 additions and 22 deletions
|
|
@ -27,7 +27,7 @@ extension Services {
|
|||
if let task = task as? BGAppRefreshTask {
|
||||
EventTracker.trackForDebugging("executing app.omnivore.fetchLinkedItems bg task")
|
||||
logger.debug("in background task register closure")
|
||||
startBackgroundFetch(task: task)
|
||||
performBackgroundFetch(task: task)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -45,7 +45,8 @@ extension Services {
|
|||
}
|
||||
}
|
||||
|
||||
static func startBackgroundFetch(task: BGAppRefreshTask) {
|
||||
static func performBackgroundFetch(task: BGAppRefreshTask) {
|
||||
Services.logger.debug("starting background fetch")
|
||||
scheduleBackgroundFetch()
|
||||
let services = Services()
|
||||
|
||||
|
|
@ -54,22 +55,25 @@ extension Services {
|
|||
logger.debug("handling background fetch expiration")
|
||||
}
|
||||
|
||||
services.peformBackgroundFetch()
|
||||
EventTracker.trackForDebugging("background fetch task completed successfully")
|
||||
task.setTaskCompleted(success: true)
|
||||
}
|
||||
|
||||
func peformBackgroundFetch() {
|
||||
Services.logger.debug("starting background fetch")
|
||||
|
||||
guard authenticator.hasValidAuthToken else {
|
||||
EventTracker.trackForDebugging("background fetch failed: user does not habe a valid auth token")
|
||||
guard services.authenticator.hasValidAuthToken else {
|
||||
EventTracker.trackForDebugging("background fetch failed: user does not have a valid auth token")
|
||||
Services.logger.debug("background fetch failed: user does not habe a valid auth token")
|
||||
task.setTaskCompleted(success: false)
|
||||
return
|
||||
}
|
||||
|
||||
Services.logger.debug("ayo this is a background task!")
|
||||
// TODO: perform tasks using data service
|
||||
Task {
|
||||
do {
|
||||
try await services.dataService.fetchLinkedItemsBackgroundTask()
|
||||
logger.debug("fetch complete")
|
||||
EventTracker.trackForDebugging("background fetch task completed successfully")
|
||||
task.setTaskCompleted(success: true)
|
||||
} catch {
|
||||
logger.debug("fetch failed")
|
||||
EventTracker.trackForDebugging("background fetch task failed")
|
||||
task.setTaskCompleted(success: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -98,7 +98,9 @@ import Views
|
|||
isLoading = false
|
||||
receivedIdx = thisSearchIdx
|
||||
cursor = queryResult.cursor
|
||||
await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID))
|
||||
if let username = dataService.currentViewer?.username {
|
||||
await dataService.prefetchPages(itemIDs: newItems.map(\.unwrappedID), username: username)
|
||||
}
|
||||
} else {
|
||||
updateFetchController(dataService: dataService)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,57 @@
|
|||
import CoreData
|
||||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
extension DataService {
|
||||
public func fetchLinkedItemsBackgroundTask() async throws {
|
||||
// Query the server for item IDs and compare against CoreData
|
||||
// to see what we're missing
|
||||
let missingItemIds = try await fetchMissingItemIDs()
|
||||
guard !missingItemIds.isEmpty else { return }
|
||||
|
||||
let username: String? = await backgroundContext.perform(schedule: .immediate) {
|
||||
let fetchRequest: NSFetchRequest<Models.Viewer> = Viewer.fetchRequest()
|
||||
fetchRequest.fetchLimit = 1 // we should only have one viewer saved
|
||||
return try? self.backgroundContext.fetch(fetchRequest).first?.username
|
||||
}
|
||||
|
||||
guard let username = username else {
|
||||
throw BasicError.message(messageText: "could not retrieve username from core data")
|
||||
}
|
||||
|
||||
// Fetch the items
|
||||
for itemID in missingItemIds { // TOOD: run these in parallel
|
||||
logger.debug("fetching item with ID: \(itemID)")
|
||||
_ = try await articleContent(username: username, itemID: itemID, useCache: false)
|
||||
logger.debug("done fetching item with ID: \(itemID)")
|
||||
}
|
||||
}
|
||||
|
||||
func fetchMissingItemIDs(previouslyFetchedIDs: [String] = [], cursor: String? = nil) async throws -> [String] {
|
||||
logger.debug("fetching more IDS: \(cursor ?? "no cursor")")
|
||||
let maxItemCount = 30
|
||||
let fetchResult = try await fetchLinkedItemIDs(limit: 10, cursor: cursor)
|
||||
let newItemsToFetch = await itemsNotInStore(from: fetchResult.itemIDs)
|
||||
let itemsToFetch = previouslyFetchedIDs + newItemsToFetch
|
||||
|
||||
if newItemsToFetch.isEmpty || itemsToFetch.count > maxItemCount || fetchResult.cursor == nil {
|
||||
return itemsToFetch
|
||||
} else {
|
||||
return try await fetchMissingItemIDs(previouslyFetchedIDs: itemsToFetch, cursor: fetchResult.cursor)
|
||||
}
|
||||
}
|
||||
|
||||
func itemsNotInStore(from itemIDs: [String]) async -> [String] {
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "id IN %@", itemIDs)
|
||||
|
||||
return await backgroundContext.perform(schedule: .immediate) {
|
||||
if let foundIDs = (try? self.backgroundContext.fetch(fetchRequest).map(\.unwrappedID)) {
|
||||
return itemIDs.filter { !foundIDs.contains($0) }
|
||||
} else {
|
||||
return []
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,9 +9,9 @@ extension DataService {
|
|||
let retryCount: Int
|
||||
}
|
||||
|
||||
public func prefetchPages(itemIDs: [String]) async {
|
||||
guard let username = currentViewer?.username else { return }
|
||||
|
||||
public func prefetchPages(itemIDs: [String], username: String) async {
|
||||
// TODO: make this concurrent
|
||||
// TODO: make a non-pending page option for BG tasks
|
||||
for itemID in itemIDs {
|
||||
await prefetchPage(pendingLink: PendingLink(itemID: itemID, retryCount: 1), username: username)
|
||||
}
|
||||
|
|
@ -183,9 +183,8 @@ extension DataService {
|
|||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "id == %@", item.id)
|
||||
|
||||
let linkedItem = try? self.backgroundContext.fetch(fetchRequest).first
|
||||
|
||||
guard let linkedItem = linkedItem else { return }
|
||||
let existingItem = try? self.backgroundContext.fetch(fetchRequest).first
|
||||
let linkedItem = existingItem ?? LinkedItem(entity: LinkedItem.entity(), insertInto: self.backgroundContext)
|
||||
|
||||
let highlightObjects = highlights.map {
|
||||
$0.asManagedObject(context: self.backgroundContext)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,67 @@
|
|||
import Foundation
|
||||
import Models
|
||||
import SwiftGraphQL
|
||||
|
||||
extension DataService {
|
||||
struct LinkedItemIDFetchResult {
|
||||
let itemIDs: [String]
|
||||
let cursor: String?
|
||||
}
|
||||
|
||||
func fetchLinkedItemIDs(limit: Int, cursor: String?) async throws -> LinkedItemIDFetchResult {
|
||||
enum QueryResult {
|
||||
case success(result: LinkedItemIDFetchResult)
|
||||
case error(error: String)
|
||||
}
|
||||
|
||||
let articleIDSelection = Selection.SearchItemEdge {
|
||||
try $0.node(selection: Selection.SearchItem { try $0.id() })
|
||||
}
|
||||
|
||||
let selection = Selection<QueryResult, Unions.SearchResult> {
|
||||
try $0.on(
|
||||
searchError: .init {
|
||||
QueryResult.error(error: try $0.errorCodes().description)
|
||||
},
|
||||
searchSuccess: .init {
|
||||
QueryResult.success(
|
||||
result: LinkedItemIDFetchResult(
|
||||
itemIDs: try $0.edges(selection: articleIDSelection.list),
|
||||
cursor: try $0.pageInfo(selection: Selection.PageInfo {
|
||||
try $0.endCursor()
|
||||
})
|
||||
)
|
||||
)
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let query = Selection.Query {
|
||||
try $0.search(
|
||||
after: OptionalArgument(cursor),
|
||||
first: OptionalArgument(limit),
|
||||
query: OptionalArgument(nil),
|
||||
selection: selection
|
||||
)
|
||||
}
|
||||
|
||||
let path = appEnvironment.graphqlPath
|
||||
let headers = networker.defaultHeaders
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
send(query, to: path, headers: headers) { queryResult in
|
||||
guard let payload = try? queryResult.get() else {
|
||||
continuation.resume(throwing: BasicError.message(messageText: "network error"))
|
||||
return
|
||||
}
|
||||
|
||||
switch payload.data {
|
||||
case let .success(result: result):
|
||||
continuation.resume(returning: result)
|
||||
case .error:
|
||||
continuation.resume(throwing: BasicError.message(messageText: "LinkedItem fetch error"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,7 +54,6 @@ struct InternalLinkedItem {
|
|||
}
|
||||
|
||||
extension Sequence where Element == InternalLinkedItem {
|
||||
// TODO: -optimization use batch update?
|
||||
func persist(context: NSManagedObjectContext) -> [LinkedItem]? {
|
||||
var linkedItems: [LinkedItem]?
|
||||
context.performAndWait {
|
||||
|
|
|
|||
Loading…
Reference in a new issue