Make the full fetchContent process a single await
This will ensure the full pages content is fully downloaded before it returns.
This commit is contained in:
@ -143,8 +143,8 @@ extension DataService {
|
||||
let path = appEnvironment.graphqlPath
|
||||
let headers = networker.defaultHeaders
|
||||
|
||||
return try await withCheckedThrowingContinuation { continuation in
|
||||
send(query, to: path, headers: headers) { [weak self] queryResult in
|
||||
let result: ArticleProps = try await withCheckedThrowingContinuation { continuation in
|
||||
send(query, to: path, headers: headers) { queryResult in
|
||||
guard let payload = try? queryResult.get() else {
|
||||
continuation.resume(throwing: ContentFetchError.network)
|
||||
return
|
||||
@ -159,130 +159,124 @@ extension DataService {
|
||||
continuation.resume(throwing: ContentFetchError.badData)
|
||||
return
|
||||
}
|
||||
|
||||
if status == .succeeded || result.item.isPDF {
|
||||
do {
|
||||
try self?.persistArticleContent(
|
||||
item: result.item,
|
||||
htmlContent: result.htmlContent,
|
||||
highlights: result.highlights
|
||||
)
|
||||
} catch {
|
||||
var message = "unknown error"
|
||||
let basicError = (error as? BasicError) ?? BasicError.message(messageText: "unknown error")
|
||||
if case let BasicError.message(messageText) = basicError {
|
||||
message = messageText
|
||||
}
|
||||
continuation.resume(throwing: ContentFetchError.unknown(description: message))
|
||||
}
|
||||
}
|
||||
|
||||
let articleContent = ArticleContent(
|
||||
title: result.item.title,
|
||||
htmlContent: result.htmlContent,
|
||||
highlightsJSONString: result.highlights.asJSONString,
|
||||
contentStatus: result.item.isPDF ? .succeeded : .make(from: result.contentStatus)
|
||||
)
|
||||
|
||||
continuation.resume(returning: articleContent)
|
||||
continuation.resume(returning: result)
|
||||
case .error:
|
||||
continuation.resume(throwing: ContentFetchError.badData)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let articleContent = ArticleContent(
|
||||
title: result.item.title,
|
||||
htmlContent: result.htmlContent,
|
||||
highlightsJSONString: result.highlights.asJSONString,
|
||||
contentStatus: result.item.isPDF ? .succeeded : .make(from: result.contentStatus)
|
||||
)
|
||||
|
||||
if result.contentStatus == .succeeded || result.item.isPDF {
|
||||
do {
|
||||
try await persistArticleContent(
|
||||
item: result.item,
|
||||
htmlContent: result.htmlContent,
|
||||
highlights: result.highlights
|
||||
)
|
||||
} catch {
|
||||
var message = "unknown error"
|
||||
let basicError = (error as? BasicError) ?? BasicError.message(messageText: "unknown error")
|
||||
if case let BasicError.message(messageText) = basicError {
|
||||
message = messageText
|
||||
}
|
||||
throw ContentFetchError.unknown(description: message)
|
||||
}
|
||||
}
|
||||
|
||||
return articleContent
|
||||
}
|
||||
|
||||
func persistArticleContent(item: InternalLinkedItem, htmlContent: String, highlights: [InternalHighlight]) throws {
|
||||
Task {
|
||||
try await backgroundContext.perform { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "id == %@", item.id)
|
||||
func persistArticleContent(item: InternalLinkedItem, htmlContent: String, highlights: [InternalHighlight]) async throws {
|
||||
try await backgroundContext.perform { [weak self] in
|
||||
guard let self = self else { return }
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "id == %@", item.id)
|
||||
|
||||
let existingItem = try? self.backgroundContext.fetch(fetchRequest).first
|
||||
let linkedItem = existingItem ?? LinkedItem(entity: LinkedItem.entity(), insertInto: self.backgroundContext)
|
||||
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)
|
||||
}
|
||||
linkedItem.addToHighlights(NSSet(array: highlightObjects))
|
||||
linkedItem.htmlContent = htmlContent
|
||||
linkedItem.id = item.id
|
||||
linkedItem.title = item.title
|
||||
linkedItem.createdAt = item.createdAt
|
||||
linkedItem.savedAt = item.savedAt
|
||||
linkedItem.readingProgress = item.readingProgress
|
||||
linkedItem.readingProgressAnchor = Int64(item.readingProgressAnchor)
|
||||
linkedItem.imageURLString = item.imageURLString
|
||||
linkedItem.onDeviceImageURLString = item.onDeviceImageURLString
|
||||
linkedItem.pageURLString = item.pageURLString
|
||||
linkedItem.descriptionText = item.descriptionText
|
||||
linkedItem.publisherURLString = item.publisherURLString
|
||||
linkedItem.author = item.author
|
||||
linkedItem.publishDate = item.publishDate
|
||||
linkedItem.slug = item.slug
|
||||
linkedItem.isArchived = item.isArchived
|
||||
linkedItem.contentReader = item.contentReader
|
||||
linkedItem.serverSyncStatus = Int64(ServerSyncStatus.isNSync.rawValue)
|
||||
let highlightObjects = highlights.map {
|
||||
$0.asManagedObject(context: self.backgroundContext)
|
||||
}
|
||||
linkedItem.addToHighlights(NSSet(array: highlightObjects))
|
||||
linkedItem.htmlContent = htmlContent
|
||||
linkedItem.id = item.id
|
||||
linkedItem.state = item.state
|
||||
linkedItem.title = item.title
|
||||
linkedItem.createdAt = item.createdAt
|
||||
linkedItem.savedAt = item.savedAt
|
||||
linkedItem.readingProgress = item.readingProgress
|
||||
linkedItem.readingProgressAnchor = Int64(item.readingProgressAnchor)
|
||||
linkedItem.imageURLString = item.imageURLString
|
||||
linkedItem.onDeviceImageURLString = item.onDeviceImageURLString
|
||||
linkedItem.pageURLString = item.pageURLString
|
||||
linkedItem.descriptionText = item.descriptionText
|
||||
linkedItem.publisherURLString = item.publisherURLString
|
||||
linkedItem.author = item.author
|
||||
linkedItem.publishDate = item.publishDate
|
||||
linkedItem.slug = item.slug
|
||||
linkedItem.isArchived = item.isArchived
|
||||
linkedItem.contentReader = item.contentReader
|
||||
linkedItem.serverSyncStatus = Int64(ServerSyncStatus.isNSync.rawValue)
|
||||
}
|
||||
|
||||
if linkedItem.isPDF, linkedItem.localPdfURL == nil {
|
||||
do {
|
||||
try self.fetchPDFData(slug: linkedItem.unwrappedSlug, pageURLString: linkedItem.unwrappedPageURLString)
|
||||
} catch {
|
||||
throw error
|
||||
}
|
||||
}
|
||||
if item.isPDF {
|
||||
try await fetchPDFData(slug: item.slug, pageURLString: item.pageURLString)
|
||||
}
|
||||
|
||||
do {
|
||||
try self.backgroundContext.save()
|
||||
logger.debug("ArticleContent saved succesfully")
|
||||
} catch {
|
||||
self.backgroundContext.rollback()
|
||||
logger.debug("Failed to save ArticleContent")
|
||||
throw error
|
||||
}
|
||||
try await backgroundContext.perform { [weak self] in
|
||||
do {
|
||||
try self?.backgroundContext.save()
|
||||
logger.debug("ArticleContent saved succesfully")
|
||||
} catch {
|
||||
self?.backgroundContext.rollback()
|
||||
logger.debug("Failed to save ArticleContent")
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func fetchPDFData(slug: String, pageURLString: String) throws {
|
||||
Task {
|
||||
guard let url = URL(string: pageURLString) else { return }
|
||||
let result: (Data, URLResponse)? = try? await URLSession.shared.data(from: url)
|
||||
guard let httpResponse = result?.1 as? HTTPURLResponse, 200 ..< 300 ~= httpResponse.statusCode else {
|
||||
throw BasicError.message(messageText: "pdfFetch failed. no response or bad status code.")
|
||||
}
|
||||
guard let data = result?.0 else {
|
||||
throw BasicError.message(messageText: "pdfFetch failed. no data received.")
|
||||
func fetchPDFData(slug: String, pageURLString: String) async throws {
|
||||
guard let url = URL(string: pageURLString) else { return }
|
||||
let result: (Data, URLResponse)? = try? await URLSession.shared.data(from: url)
|
||||
guard let httpResponse = result?.1 as? HTTPURLResponse, 200 ..< 300 ~= httpResponse.statusCode else {
|
||||
throw BasicError.message(messageText: "pdfFetch failed. no response or bad status code.")
|
||||
}
|
||||
guard let data = result?.0 else {
|
||||
throw BasicError.message(messageText: "pdfFetch failed. no data received.")
|
||||
}
|
||||
|
||||
try await backgroundContext.perform { [weak self] in
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(LinkedItem.slug), slug)
|
||||
|
||||
let linkedItem = try? self?.backgroundContext.fetch(fetchRequest).first
|
||||
guard let linkedItem = linkedItem else {
|
||||
let errorMessage = "pdfFetch failed. could not find LinkedItem from fetch request"
|
||||
throw BasicError.message(messageText: errorMessage)
|
||||
}
|
||||
|
||||
try await backgroundContext.perform { [weak self] in
|
||||
let fetchRequest: NSFetchRequest<Models.LinkedItem> = LinkedItem.fetchRequest()
|
||||
fetchRequest.predicate = NSPredicate(format: "%K == %@", #keyPath(LinkedItem.slug), slug)
|
||||
let subPath = UUID().uuidString + ".pdf" // linkedItem.title.isEmpty ? UUID().uuidString : linkedItem.title
|
||||
|
||||
let linkedItem = try? self?.backgroundContext.fetch(fetchRequest).first
|
||||
guard let linkedItem = linkedItem else {
|
||||
let errorMessage = "pdfFetch failed. could not find LinkedItem from fetch request"
|
||||
throw BasicError.message(messageText: errorMessage)
|
||||
}
|
||||
let path = FileManager.default
|
||||
.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent(subPath)
|
||||
|
||||
let subPath = UUID().uuidString + ".pdf" // linkedItem.title.isEmpty ? UUID().uuidString : linkedItem.title
|
||||
|
||||
let path = FileManager.default
|
||||
.urls(for: .cachesDirectory, in: .userDomainMask)[0]
|
||||
.appendingPathComponent(subPath)
|
||||
|
||||
do {
|
||||
try data.write(to: path)
|
||||
linkedItem.localPdfURL = path.absoluteString
|
||||
try self?.backgroundContext.save()
|
||||
logger.debug("PDF data saved succesfully")
|
||||
} catch {
|
||||
self?.backgroundContext.rollback()
|
||||
logger.debug("PDF data saved succesfully")
|
||||
let errorMessage = "pdfFetch failed. core data save failed."
|
||||
throw BasicError.message(messageText: errorMessage)
|
||||
}
|
||||
do {
|
||||
try data.write(to: path)
|
||||
linkedItem.localPdfURL = path.absoluteString
|
||||
try self?.backgroundContext.save()
|
||||
} catch {
|
||||
self?.backgroundContext.rollback()
|
||||
let errorMessage = "pdfFetch failed. core data save failed."
|
||||
throw BasicError.message(messageText: errorMessage)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -346,9 +340,6 @@ extension DataService {
|
||||
} else {
|
||||
try await saveURL(id: id, url: url)
|
||||
}
|
||||
try backgroundContext.performAndWait {
|
||||
try backgroundContext.save()
|
||||
}
|
||||
} catch {
|
||||
// We don't propogate these errors, we just let it pass through so
|
||||
// the user can attempt to fetch content again.
|
||||
|
||||
Reference in New Issue
Block a user