From 9b9191b6b1de1a87e5be905ad62a336d2dd644a3 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 27 May 2022 08:42:34 -0700 Subject: [PATCH 1/5] clear core data when switch env --- .../Services/DataService/DataService.swift | 26 +++++++++++-------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 6878a40dc..5c458ea3c 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -59,9 +59,8 @@ public final class DataService: ObservableObject { await networker.hasConnectionAndValidToken() } - private func resetCoreData() { - let storeContainer = - persistentContainer.persistentStoreCoordinator + private func clearCoreData() { + let storeContainer = persistentContainer.persistentStoreCoordinator do { for store in storeContainer.persistentStores { @@ -71,18 +70,23 @@ public final class DataService: ObservableObject { options: nil ) } - persistentContainer = PersistentContainer.make() - persistentContainer.loadPersistentStores { _, error in - if let error = error { - fatalError("Core Data store failed to load with error: \(error)") - } - } - backgroundContext = persistentContainer.newBackgroundContext() } catch { - logger.debug("Failed to reset core data stores") + logger.debug("Failed to clear core data stores") } } + private func resetCoreData() { + clearCoreData() + + persistentContainer = PersistentContainer.make() + persistentContainer.loadPersistentStores { _, error in + if let error = error { + fatalError("Core Data store failed to load with error: \(error)") + } + } + backgroundContext = persistentContainer.newBackgroundContext() + } + private func isFirstTimeRunningNewAppVersion() -> Bool { let appVersion = Bundle.main.object(forInfoDictionaryKey: "CFBundleShortVersionString") guard let appVersion = appVersion as? String else { return false } From 26a5b5dae1d9aa42d12c58243227dadb7d365d3d Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 27 May 2022 09:16:12 -0700 Subject: [PATCH 2/5] handle pdf fetch errors properly --- .../Services/DataService/DataService.swift | 1 + .../Queries/ArticleContentQuery.swift | 123 +++++++++++------- 2 files changed, 75 insertions(+), 49 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 5c458ea3c..8c8f02ed1 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -49,6 +49,7 @@ public final class DataService: ObservableObject { public func switchAppEnvironment(appEnvironment: AppEnvironment) { do { try ValetKey.appEnvironmentString.setValue(appEnvironment.rawValue) + clearCoreData() fatalError("App environment changed -- restarting app") } catch { fatalError("Unable to write to Keychain: \(error)") diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index 7fedac735..dde916be4 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -155,11 +155,20 @@ extension DataService { } if status == .succeeded { - self?.persistArticleContent( - item: result.item, - htmlContent: result.htmlContent, - highlights: result.highlights - ) + 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( @@ -176,72 +185,88 @@ extension DataService { } } - func persistArticleContent(item: InternalLinkedItem, htmlContent: String, highlights: [InternalHighlight]) { - 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]) 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) - 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 + 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 - if linkedItem.isPDF { - self.fetchPDFData(slug: linkedItem.unwrappedSlug, pageURLString: linkedItem.unwrappedPageURLString) - } + if linkedItem.isPDF { + do { + try self.fetchPDFData(slug: linkedItem.unwrappedSlug, pageURLString: linkedItem.unwrappedPageURLString) + } catch { + throw error + } + } - do { - try self.backgroundContext.save() - print("ArticleContent saved succesfully") - } catch { - self.backgroundContext.rollback() - print("Failed to save ArticleContent: \(error)") + 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) { + 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 { return } - guard let data = result?.0 else { return } + 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.") + } - await backgroundContext.perform { [weak self] in + 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 { return } + guard let linkedItem = linkedItem else { + let errorMessage = "pdfFetch failed. could not find LinkedItem from fetch request" + throw BasicError.message(messageText: errorMessage) + } linkedItem.pdfData = data do { try self?.backgroundContext.save() - print("PDF data saved succesfully") + logger.debug("PDF data saved succesfully") } catch { self?.backgroundContext.rollback() - print("Failed to save PDF data: \(error)") + logger.debug("PDF data saved succesfully") + let errorMessage = "pdfFetch failed. core data save failed." + throw BasicError.message(messageText: errorMessage) } } } From c08c710601dfc82db245d445454f1357da541014 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 27 May 2022 10:25:34 -0700 Subject: [PATCH 3/5] set pdf state to success even if download has not completed --- .../Services/DataService/Queries/ArticleContentQuery.swift | 4 ++-- .../Services/InternalModels/InternalLinkedItem.swift | 7 +++++++ 2 files changed, 9 insertions(+), 2 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index dde916be4..e167a1b6a 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -154,7 +154,7 @@ extension DataService { return } - if status == .succeeded { + if status == .succeeded || result.item.isPDF { do { try self?.persistArticleContent( item: result.item, @@ -174,7 +174,7 @@ extension DataService { let articleContent = ArticleContent( htmlContent: result.htmlContent, highlightsJSONString: result.highlights.asJSONString, - contentStatus: .make(from: result.contentStatus) + contentStatus: result.item.isPDF ? .succeeded : .make(from: result.contentStatus) ) continuation.resume(returning: articleContent) diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItem.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItem.swift index 5e39aa6b9..d050c5e4f 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItem.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalLinkedItem.swift @@ -23,6 +23,13 @@ struct InternalLinkedItem { let contentReader: String? var labels: [InternalLinkedItemLabel] + var isPDF: Bool { + if let contentReader = contentReader { + return contentReader == "PDF" + } + return (pageURLString ?? "").hasSuffix("pdf") + } + func asManagedObject(inContext context: NSManagedObjectContext) -> LinkedItem { let existingItem = LinkedItem.lookup(byID: id, inContext: context) let linkedItem = existingItem ?? LinkedItem(entity: LinkedItem.entity(), insertInto: context) From 2918cbcdf5c80d6afbf0442df358a07a8b0cb55b Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 27 May 2022 10:44:08 -0700 Subject: [PATCH 4/5] fetch pdf data only if it's missing from LinkedItem --- .../Services/DataService/Queries/ArticleContentQuery.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index e167a1b6a..b954c3a82 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -217,7 +217,7 @@ extension DataService { linkedItem.isArchived = item.isArchived linkedItem.contentReader = item.contentReader - if linkedItem.isPDF { + if linkedItem.isPDF, linkedItem.pdfData == nil { do { try self.fetchPDFData(slug: linkedItem.unwrappedSlug, pageURLString: linkedItem.unwrappedPageURLString) } catch { From b312b6f00be5e36b3c86d51d99a9a92d4ebe4026 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Sat, 28 May 2022 07:53:12 -0700 Subject: [PATCH 5/5] bump ios to 1.8.0 --- apple/Omnivore.xcodeproj/project.pbxproj | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/apple/Omnivore.xcodeproj/project.pbxproj b/apple/Omnivore.xcodeproj/project.pbxproj index bd10aa72a..0229d82b5 100644 --- a/apple/Omnivore.xcodeproj/project.pbxproj +++ b/apple/Omnivore.xcodeproj/project.pbxproj @@ -1239,7 +1239,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac"; @@ -1270,7 +1270,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.8.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1352,7 +1352,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; @@ -1386,7 +1386,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.8.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1441,7 +1441,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7.1; + MARKETING_VERSION = 1.8.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1473,7 +1473,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.7.1; + MARKETING_VERSION = 1.8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -1512,7 +1512,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.7.1; + MARKETING_VERSION = 1.8.0; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "-framework", @@ -1551,7 +1551,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.8.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -1589,7 +1589,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.7.0; + MARKETING_VERSION = 1.8.0; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "-framework", @@ -1674,7 +1674,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.7.1; + MARKETING_VERSION = 1.8.0; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension"; PRODUCT_NAME = ShareExtension; SDKROOT = iphoneos; @@ -1728,7 +1728,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.7.1; + MARKETING_VERSION = 1.8.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1756,7 +1756,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.7.1; + MARKETING_VERSION = 1.8.0; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension"; PRODUCT_NAME = ShareExtension; SDKROOT = iphoneos;