From fb1027dc38001e2ada5aa080ee5b7e6141c7d8f3 Mon Sep 17 00:00:00 2001 From: Jackson Harper Date: Wed, 8 Jun 2022 10:45:04 -0700 Subject: [PATCH] Make the full fetchContent process a single await This will ensure the full pages content is fully downloaded before it returns. --- .../Queries/ArticleContentQuery.swift | 211 +++++++++--------- 1 file changed, 101 insertions(+), 110 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index 04549bd18..81f992514 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -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 = 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 = 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 = 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 = 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.