From 31061abdcd8013839b1c02928e23b095e821475f Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 14 Apr 2022 18:17:12 -0700 Subject: [PATCH 01/76] add coredata model to Services package --- .../App/Views/Home/HomeFeedViewModel.swift | 2 +- .../CoreDataModel.xcdatamodel/contents | 4 +++ .../Services/DataService/DataService.swift | 34 ++++++++----------- 3 files changed, 20 insertions(+), 20 deletions(-) create mode 100644 apple/OmnivoreKit/Sources/Services/DataService/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index ed61a4341..0460a1578 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -43,7 +43,7 @@ import Views // Check if user has scrolled to the last five items in the list if let itemIndex = itemIndex, itemIndex > thresholdIndex, items.count < thresholdIndex + 10 { - Task { await loadItems(dataService: dataService, isRefresh: false) } + loadItems(dataService: dataService, isRefresh: false) } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Services/DataService/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents new file mode 100644 index 000000000..12986cc36 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/DataService/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 12e72e406..74ee001a8 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -1,15 +1,9 @@ import Combine +import CoreData import Foundation import Models -public class CacheManager: NSObject, NSCacheDelegate { - public func cache(_: NSCache, willEvictObject obj: Any) { - // This is just used for debugging - if let content = obj as? CachedPageContent { - print("evicting page from cache", content.slug) - } - } -} +final class PersistentContainer: NSPersistentContainer {} public final class DataService: ObservableObject { public static var registerIntercomUser: ((String) -> Void)? @@ -25,14 +19,23 @@ public final class DataService: ObservableObject { let highlightsCache = NSCache() let highlightsCacheQueue = DispatchQueue(label: "app.omnivore.highlights.cache.queue", attributes: .concurrent) - let cacheManager: CacheManager + let persistentContainer: PersistentContainer var subscriptions = Set() public init(appEnvironment: AppEnvironment, networker: Networker) { self.appEnvironment = appEnvironment self.networker = networker - self.cacheManager = CacheManager() - pageCache.delegate = cacheManager + self.persistentContainer = { + let modelURL = Bundle.module.url(forResource: "CoreDataModel", withExtension: "momd")! + let model = NSManagedObjectModel(contentsOf: modelURL)! + return PersistentContainer(name: "DataModel", managedObjectModel: model) + }() + + persistentContainer.loadPersistentStores { _, error in + if let error = error { + fatalError("Core Data store failed to load with error: \(error)") + } + } } public func clearHighlights() { @@ -51,7 +54,6 @@ public final class DataService: ObservableObject { public extension DataService { func prefetchPages(items: [FeedItem]) { - print("prefetching pages") guard let viewer = currentViewer else { return } for item in items { @@ -67,13 +69,7 @@ public extension DataService { } func pageFromCache(slug: String) -> ArticleContent? { - if let content = pageCache.object(forKey: NSString(string: slug)) { - print("cache hit", slug) - return content.value - } else { - print("cache miss", slug) - } - return nil + pageCache.object(forKey: NSString(string: slug))?.value } func invalidateCachedPage(slug: String?) { From bb11fac0554930f153aa0d1e21082a42821a18ac Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Thu, 14 Apr 2022 21:10:44 -0700 Subject: [PATCH 02/76] remove codingkeys and decaodable conformance from FeedItem --- .../OmnivoreKit/Sources/Models/FeedItem.swift | 73 ++++++++++--------- .../Services/DataService/DataService.swift | 2 + 2 files changed, 42 insertions(+), 33 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Models/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/FeedItem.swift index 42ff019ea..c791b3f2c 100644 --- a/apple/OmnivoreKit/Sources/Models/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/FeedItem.swift @@ -10,7 +10,7 @@ public struct HomeFeedData { } } -public struct FeedItem: Identifiable, Hashable, Decodable { +public struct FeedItem: Identifiable, Hashable { public let id: String public let title: String public let createdAt: Date @@ -70,39 +70,8 @@ public struct FeedItem: Identifiable, Hashable, Decodable { self.labels = labels } - enum CodingKeys: String, CodingKey { - // swiftlint:disable:next line_length - case id, title, createdAt, savedAt, image, isArchived, readingProgressPercent, readingProgressAnchorIndex, slug, contentReader, url, labels - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - id = try container.decode(String.self, forKey: .id) - title = try container.decode(String.self, forKey: .title) - createdAt = try container.decode(Date.self, forKey: .createdAt) - savedAt = try container.decode(Date.self, forKey: .savedAt) - description = try container.decode(String?.self, forKey: .title) - imageURLString = try container.decode(String?.self, forKey: .image) - readingProgress = try container.decode(Double.self, forKey: .readingProgressPercent) - readingProgressAnchor = try container.decode(Int.self, forKey: .readingProgressAnchorIndex) - slug = try container.decode(String.self, forKey: .slug) - contentReader = try container.decode(String.self, forKey: .contentReader) - pageURLString = try container.decode(String.self, forKey: .url) - isArchived = try container.decode(Bool.self, forKey: .isArchived) - labels = try container.decode([FeedItemLabel].self, forKey: .labels) - - self.onDeviceImageURLString = nil - self.documentDirectoryPath = nil - self.publisherURLString = nil - self.author = nil - self.publishDate = nil - } - public static func fromJsonArticle(linkData: Data) -> FeedItem? { - if let item = try? JSONDecoder().decode(FeedItem.self, from: linkData) { - return item - } - return nil + try? JSONDecoder().decode(JSONArticle.self, from: linkData).feedItem } public var isRead: Bool { @@ -130,3 +99,41 @@ public struct FeedItem: Identifiable, Hashable, Decodable { return documentDirectoryURL ?? URL(string: pageURLString) } } + +/// Internal model used for parsing a push notification object only +struct JSONArticle: Decodable { + let id: String + let title: String + let createdAt: Date + let savedAt: Date + let image: String + let readingProgressPercent: Double + let readingProgressAnchorIndex: Int + let slug: String + let contentReader: String + let url: String + let isArchived: Bool + + var feedItem: FeedItem { + FeedItem( + id: id, + title: title, + createdAt: createdAt, + savedAt: savedAt, + readingProgress: readingProgressPercent, + readingProgressAnchor: readingProgressAnchorIndex, + imageURLString: image, + onDeviceImageURLString: nil, + documentDirectoryPath: nil, + pageURLString: url, + description: title, + publisherURLString: nil, + author: nil, + publishDate: nil, + slug: slug, + isArchived: isArchived, + contentReader: contentReader, + labels: [] + ) + } +} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 74ee001a8..0c6f7157a 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -3,6 +3,8 @@ import CoreData import Foundation import Models +/// An `NSPersistentContainer` subclass that lives in the `Services` package so that +/// the data model is looked for in the same package bundle (rather than the main bundle) final class PersistentContainer: NSPersistentContainer {} public final class DataService: ObservableObject { From 5ff6d6789be4bf6db148a36b7daab60e62b1917d Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 15 Apr 2022 13:25:58 -0700 Subject: [PATCH 03/76] create managedobject models for feeditem and feeditemlabel --- .../OmnivoreKit/Sources/Models/FeedItem.swift | 66 +++++++++++++++++-- .../Sources/Models/FeedItemLabel.swift | 33 +++++++++- .../Queries/LibraryItemsQuery.swift | 2 +- .../Selections/FeedItemLabelSelection.swift | 2 +- .../Sources/Views/FeedItem/GridCard.swift | 2 +- 5 files changed, 95 insertions(+), 10 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Models/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/FeedItem.swift index c791b3f2c..806b66dc3 100644 --- a/apple/OmnivoreKit/Sources/Models/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/FeedItem.swift @@ -1,3 +1,4 @@ +import CoreData import Foundation public struct HomeFeedData { @@ -10,6 +11,29 @@ public struct HomeFeedData { } } +public class FeedItemManagedObject: NSManagedObject { + static let entityName = "FeedItemManagedObject" + + @NSManaged public var id: String + @NSManaged public var title: String + @NSManaged public var createdAt: Date + @NSManaged public var savedAt: Date + @NSManaged public var readingProgress: Double + @NSManaged public var readingProgressAnchor: Int + @NSManaged public var imageURLString: String? + @NSManaged public var onDeviceImageURLString: String? + @NSManaged public var documentDirectoryPath: String? + @NSManaged public var pageURLString: String + @NSManaged public var descriptionText: String? + @NSManaged public var publisherURLString: String? + @NSManaged public var author: String? + @NSManaged public var publishDate: Date? + @NSManaged public var slug: String + @NSManaged public var isArchived: Bool + @NSManaged public var contentReader: String? + @NSManaged public var labels: Set +} + public struct FeedItem: Identifiable, Hashable { public let id: String public let title: String @@ -21,7 +45,7 @@ public struct FeedItem: Identifiable, Hashable { public let onDeviceImageURLString: String? public let documentDirectoryPath: String? public let pageURLString: String - public let description: String? + public let descriptionText: String? public let publisherURLString: String? public let author: String? public let publishDate: Date? @@ -41,7 +65,7 @@ public struct FeedItem: Identifiable, Hashable { onDeviceImageURLString: String?, documentDirectoryPath: String?, pageURLString: String, - description: String?, + descriptionText: String?, publisherURLString: String?, author: String?, publishDate: Date?, @@ -60,7 +84,7 @@ public struct FeedItem: Identifiable, Hashable { self.onDeviceImageURLString = onDeviceImageURLString self.documentDirectoryPath = documentDirectoryPath self.pageURLString = pageURLString - self.description = description + self.descriptionText = descriptionText self.publisherURLString = publisherURLString self.author = author self.publishDate = publishDate @@ -70,6 +94,40 @@ public struct FeedItem: Identifiable, Hashable { self.labels = labels } + func toManagedObject(inContext context: NSManagedObjectContext) -> FeedItemManagedObject? { + guard let entityDescription = NSEntityDescription.entity(forEntityName: FeedItemManagedObject.entityName, in: context) else { + print("Failed to create \(FeedItemManagedObject.entityName)") + return nil + } + + let object = FeedItemManagedObject(entity: entityDescription, insertInto: context) + object.id = id + object.title = title + object.createdAt = createdAt + object.savedAt = savedAt + object.readingProgress = readingProgress + object.readingProgressAnchor = readingProgressAnchor + object.imageURLString = imageURLString + object.onDeviceImageURLString = onDeviceImageURLString + object.documentDirectoryPath = documentDirectoryPath + object.pageURLString = pageURLString + object.descriptionText = descriptionText + object.publisherURLString = publisherURLString + object.author = author + object.publishDate = publishDate + object.slug = slug + object.isArchived = isArchived + object.contentReader = contentReader + + for label in labels { + if let managedLabel = label.toManagedObject(inContext: context) { + object.labels.insert(managedLabel) + } + } + + return object + } + public static func fromJsonArticle(linkData: Data) -> FeedItem? { try? JSONDecoder().decode(JSONArticle.self, from: linkData).feedItem } @@ -126,7 +184,7 @@ struct JSONArticle: Decodable { onDeviceImageURLString: nil, documentDirectoryPath: nil, pageURLString: url, - description: title, + descriptionText: title, publisherURLString: nil, author: nil, publishDate: nil, diff --git a/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift b/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift index 14a157a02..04161da22 100644 --- a/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift +++ b/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift @@ -1,3 +1,4 @@ +import CoreData import Foundation public struct FeedItemLabel: Decodable, Hashable { @@ -5,19 +6,45 @@ public struct FeedItemLabel: Decodable, Hashable { public let name: String public let color: String public let createdAt: Date? - public let description: String? + public let labelDescription: String? public init( id: String, name: String, color: String, createdAt: Date?, - description: String? + labelDescription: String? ) { self.id = id self.name = name self.color = color self.createdAt = createdAt - self.description = description + self.labelDescription = labelDescription + } + + func toManagedObject(inContext context: NSManagedObjectContext) -> FeedItemLabelManagedObject? { + let entityName = FeedItemLabelManagedObject.entityName + guard let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context) else { + print("Failed to create \(entityName)") + return nil + } + + let object = FeedItemLabelManagedObject(entity: entityDescription, insertInto: context) + object.id = id + object.name = name + object.color = color + object.createdAt = createdAt + object.labelDescription = labelDescription + return object } } + +public class FeedItemLabelManagedObject: NSManagedObject { + static let entityName = "FeedItemLabelManagedObject" + + @NSManaged public var id: String + @NSManaged public var name: String + @NSManaged public var color: String + @NSManaged public var createdAt: Date? + @NSManaged public var labelDescription: String? +} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift index 532ffe88f..f2aaa530d 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift @@ -137,7 +137,7 @@ let homeFeedItemSelection = Selection.Article { onDeviceImageURLString: nil, documentDirectoryPath: nil, pageURLString: try $0.url(), - description: try $0.description(), + descriptionText: try $0.description(), publisherURLString: try $0.originalArticleUrl(), author: try $0.author(), publishDate: try $0.publishedAt()?.value, diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Selections/FeedItemLabelSelection.swift b/apple/OmnivoreKit/Sources/Services/DataService/Selections/FeedItemLabelSelection.swift index 998dc8dea..2b0abade0 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Selections/FeedItemLabelSelection.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Selections/FeedItemLabelSelection.swift @@ -7,6 +7,6 @@ let feedItemLabelSelection = Selection.Label { name: try $0.name(), color: try $0.color(), createdAt: try $0.createdAt()?.value, - description: try $0.description() + labelDescription: try $0.description() ) } diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift index 692e430ad..3d75ed4b0 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift @@ -117,7 +117,7 @@ public struct GridCard: View { // Link description and image HStack(alignment: .top) { - Text(item.description ?? item.title) + Text(item.descriptionText ?? item.title) .font(.appSubheadline) .foregroundColor(.appGrayTextContrast) .lineLimit(nil) From 0264564a6433425e4f15f5dcdd8bef20bc1c08b5 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 15 Apr 2022 13:35:55 -0700 Subject: [PATCH 04/76] move coredata model into models package --- .../CoreDataModel.xcdatamodel/contents | 0 .../Sources/Models/CoreData/StorageProvider.swift | 12 ++++++++++++ apple/OmnivoreKit/Sources/Models/FeedItem.swift | 4 ++-- .../Sources/Services/DataService/DataService.swift | 10 +--------- 4 files changed, 15 insertions(+), 11 deletions(-) rename apple/OmnivoreKit/Sources/{Services/DataService => Models}/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents (100%) create mode 100644 apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift diff --git a/apple/OmnivoreKit/Sources/Services/DataService/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents similarity index 100% rename from apple/OmnivoreKit/Sources/Services/DataService/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents rename to apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift new file mode 100644 index 000000000..1f42124dd --- /dev/null +++ b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift @@ -0,0 +1,12 @@ +import CoreData +import Foundation + +/// An `NSPersistentContainer` subclass that lives in the `Models` package so that +/// the data model is looked for in the same package bundle (rather than the main bundle) +public class PersistentContainer: NSPersistentContainer { + public static func make() -> PersistentContainer { + let modelURL = Bundle.module.url(forResource: "CoreDataModel", withExtension: "momd")! + let model = NSManagedObjectModel(contentsOf: modelURL)! + return PersistentContainer(name: "DataModel", managedObjectModel: model) + } +} diff --git a/apple/OmnivoreKit/Sources/Models/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/FeedItem.swift index 806b66dc3..92d886716 100644 --- a/apple/OmnivoreKit/Sources/Models/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/FeedItem.swift @@ -118,13 +118,13 @@ public struct FeedItem: Identifiable, Hashable { object.slug = slug object.isArchived = isArchived object.contentReader = contentReader - + for label in labels { if let managedLabel = label.toManagedObject(inContext: context) { object.labels.insert(managedLabel) } } - + return object } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 0c6f7157a..f0648b797 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -3,10 +3,6 @@ import CoreData import Foundation import Models -/// An `NSPersistentContainer` subclass that lives in the `Services` package so that -/// the data model is looked for in the same package bundle (rather than the main bundle) -final class PersistentContainer: NSPersistentContainer {} - public final class DataService: ObservableObject { public static var registerIntercomUser: ((String) -> Void)? public static var showIntercomMessenger: (() -> Void)? @@ -27,11 +23,7 @@ public final class DataService: ObservableObject { public init(appEnvironment: AppEnvironment, networker: Networker) { self.appEnvironment = appEnvironment self.networker = networker - self.persistentContainer = { - let modelURL = Bundle.module.url(forResource: "CoreDataModel", withExtension: "momd")! - let model = NSManagedObjectModel(contentsOf: modelURL)! - return PersistentContainer(name: "DataModel", managedObjectModel: model) - }() + self.persistentContainer = PersistentContainer.make() persistentContainer.loadPersistentStores { _, error in if let error = error { From a879a68ad86113c5d75e7fd1e58950bc9e88e0ee Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Fri, 15 Apr 2022 18:25:11 -0700 Subject: [PATCH 05/76] define feeditem models in core data model --- .../CoreDataModel.xcdatamodel/contents | 36 +++++- .../OmnivoreKit/Sources/Models/FeedItem.swift | 117 +++++++++--------- .../Sources/Models/FeedItemLabel.swift | 54 ++++---- .../Services/DataService/DataService.swift | 1 - 4 files changed, 125 insertions(+), 83 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 12986cc36..605103808 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -1,4 +1,38 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/apple/OmnivoreKit/Sources/Models/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/FeedItem.swift index 92d886716..3e95f54c8 100644 --- a/apple/OmnivoreKit/Sources/Models/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/FeedItem.swift @@ -1,4 +1,4 @@ -import CoreData +// import CoreData import Foundation public struct HomeFeedData { @@ -11,28 +11,32 @@ public struct HomeFeedData { } } -public class FeedItemManagedObject: NSManagedObject { - static let entityName = "FeedItemManagedObject" - - @NSManaged public var id: String - @NSManaged public var title: String - @NSManaged public var createdAt: Date - @NSManaged public var savedAt: Date - @NSManaged public var readingProgress: Double - @NSManaged public var readingProgressAnchor: Int - @NSManaged public var imageURLString: String? - @NSManaged public var onDeviceImageURLString: String? - @NSManaged public var documentDirectoryPath: String? - @NSManaged public var pageURLString: String - @NSManaged public var descriptionText: String? - @NSManaged public var publisherURLString: String? - @NSManaged public var author: String? - @NSManaged public var publishDate: Date? - @NSManaged public var slug: String - @NSManaged public var isArchived: Bool - @NSManaged public var contentReader: String? - @NSManaged public var labels: Set -} +// public class FeedItemManagedObject: NSManagedObject { +// static let entityName = "FeedItemManagedObject" +// +// @nonobjc public class func fetchRequest() -> NSFetchRequest { +// NSFetchRequest(entityName: entityName) +// } +// +// @NSManaged public var id: String +// @NSManaged public var title: String +// @NSManaged public var createdAt: Date +// @NSManaged public var savedAt: Date +// @NSManaged public var readingProgress: Double +// @NSManaged public var readingProgressAnchor: Int +// @NSManaged public var imageURLString: String? +// @NSManaged public var onDeviceImageURLString: String? +// @NSManaged public var documentDirectoryPath: String? +// @NSManaged public var pageURLString: String +// @NSManaged public var descriptionText: String? +// @NSManaged public var publisherURLString: String? +// @NSManaged public var author: String? +// @NSManaged public var publishDate: Date? +// @NSManaged public var slug: String +// @NSManaged public var isArchived: Bool +// @NSManaged public var contentReader: String? +// @NSManaged public var labels: Set +// } public struct FeedItem: Identifiable, Hashable { public let id: String @@ -94,39 +98,40 @@ public struct FeedItem: Identifiable, Hashable { self.labels = labels } - func toManagedObject(inContext context: NSManagedObjectContext) -> FeedItemManagedObject? { - guard let entityDescription = NSEntityDescription.entity(forEntityName: FeedItemManagedObject.entityName, in: context) else { - print("Failed to create \(FeedItemManagedObject.entityName)") - return nil - } - - let object = FeedItemManagedObject(entity: entityDescription, insertInto: context) - object.id = id - object.title = title - object.createdAt = createdAt - object.savedAt = savedAt - object.readingProgress = readingProgress - object.readingProgressAnchor = readingProgressAnchor - object.imageURLString = imageURLString - object.onDeviceImageURLString = onDeviceImageURLString - object.documentDirectoryPath = documentDirectoryPath - object.pageURLString = pageURLString - object.descriptionText = descriptionText - object.publisherURLString = publisherURLString - object.author = author - object.publishDate = publishDate - object.slug = slug - object.isArchived = isArchived - object.contentReader = contentReader - - for label in labels { - if let managedLabel = label.toManagedObject(inContext: context) { - object.labels.insert(managedLabel) - } - } - - return object - } +// func toManagedObject(inContext context: NSManagedObjectContext) -> FeedItemManagedObject? { +// let entityName = FeedItemManagedObject.entityName +// guard let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context) else { +// print("Failed to create \(entityName)") +// return nil +// } +// +// let object = FeedItemManagedObject(entity: entityDescription, insertInto: context) +// object.id = id +// object.title = title +// object.createdAt = createdAt +// object.savedAt = savedAt +// object.readingProgress = readingProgress +// object.readingProgressAnchor = readingProgressAnchor +// object.imageURLString = imageURLString +// object.onDeviceImageURLString = onDeviceImageURLString +// object.documentDirectoryPath = documentDirectoryPath +// object.pageURLString = pageURLString +// object.descriptionText = descriptionText +// object.publisherURLString = publisherURLString +// object.author = author +// object.publishDate = publishDate +// object.slug = slug +// object.isArchived = isArchived +// object.contentReader = contentReader +// +// for label in labels { +// if let managedLabel = label.toManagedObject(inContext: context) { +// object.labels.insert(managedLabel) +// } +// } +// +// return object +// } public static func fromJsonArticle(linkData: Data) -> FeedItem? { try? JSONDecoder().decode(JSONArticle.self, from: linkData).feedItem diff --git a/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift b/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift index 04161da22..b37af81a9 100644 --- a/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift +++ b/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift @@ -1,4 +1,4 @@ -import CoreData +// import CoreData import Foundation public struct FeedItemLabel: Decodable, Hashable { @@ -22,29 +22,33 @@ public struct FeedItemLabel: Decodable, Hashable { self.labelDescription = labelDescription } - func toManagedObject(inContext context: NSManagedObjectContext) -> FeedItemLabelManagedObject? { - let entityName = FeedItemLabelManagedObject.entityName - guard let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context) else { - print("Failed to create \(entityName)") - return nil - } - - let object = FeedItemLabelManagedObject(entity: entityDescription, insertInto: context) - object.id = id - object.name = name - object.color = color - object.createdAt = createdAt - object.labelDescription = labelDescription - return object - } +// func toManagedObject(inContext context: NSManagedObjectContext) -> FeedItemLabelManagedObject? { +// let entityName = FeedItemLabelManagedObject.entityName +// guard let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context) else { +// print("Failed to create \(entityName)") +// return nil +// } +// +// let object = FeedItemLabelManagedObject(entity: entityDescription, insertInto: context) +// object.id = id +// object.name = name +// object.color = color +// object.createdAt = createdAt +// object.labelDescription = labelDescription +// return object +// } } -public class FeedItemLabelManagedObject: NSManagedObject { - static let entityName = "FeedItemLabelManagedObject" - - @NSManaged public var id: String - @NSManaged public var name: String - @NSManaged public var color: String - @NSManaged public var createdAt: Date? - @NSManaged public var labelDescription: String? -} +// public class FeedItemLabelManagedObject: NSManagedObject { +// static let entityName = "FeedItemLabelManagedObject" +// +// @nonobjc public class func fetchRequest() -> NSFetchRequest { +// NSFetchRequest(entityName: entityName) +// } +// +// @NSManaged public var id: String +// @NSManaged public var name: String +// @NSManaged public var color: String +// @NSManaged public var createdAt: Date? +// @NSManaged public var labelDescription: String? +// } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index f0648b797..846e337c8 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -1,5 +1,4 @@ import Combine -import CoreData import Foundation import Models From 00639011d898393cd3f8e9435d477f74507e4f92 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Sat, 16 Apr 2022 09:12:10 -0700 Subject: [PATCH 06/76] read/write article content using coredata --- .../Views/WebReader/WebReaderViewModel.swift | 1 - .../CoreDataModel.xcdatamodel/contents | 2 +- .../OmnivoreKit/Sources/Models/FeedItem.swift | 63 ------------------- .../Sources/Models/FeedItemLabel.swift | 31 --------- .../Services/DataService/DataService.swift | 24 +++---- .../Queries/ArticleContentQuery.swift | 20 +++++- 6 files changed, 32 insertions(+), 109 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 2afceda35..081311164 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -39,7 +39,6 @@ final class WebReaderViewModel: ObservableObject { }, receiveValue: { [weak self] articleContent in self?.articleContent = articleContent - dataService.pageCache.setObject(CachedPageContent(slug, articleContent), forKey: NSString(string: slug)) } ) .store(in: &subscriptions) diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 605103808..31fc245a2 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -32,7 +32,7 @@ - + \ No newline at end of file diff --git a/apple/OmnivoreKit/Sources/Models/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/FeedItem.swift index 3e95f54c8..3a2b3ea83 100644 --- a/apple/OmnivoreKit/Sources/Models/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/FeedItem.swift @@ -1,4 +1,3 @@ -// import CoreData import Foundation public struct HomeFeedData { @@ -11,33 +10,6 @@ public struct HomeFeedData { } } -// public class FeedItemManagedObject: NSManagedObject { -// static let entityName = "FeedItemManagedObject" -// -// @nonobjc public class func fetchRequest() -> NSFetchRequest { -// NSFetchRequest(entityName: entityName) -// } -// -// @NSManaged public var id: String -// @NSManaged public var title: String -// @NSManaged public var createdAt: Date -// @NSManaged public var savedAt: Date -// @NSManaged public var readingProgress: Double -// @NSManaged public var readingProgressAnchor: Int -// @NSManaged public var imageURLString: String? -// @NSManaged public var onDeviceImageURLString: String? -// @NSManaged public var documentDirectoryPath: String? -// @NSManaged public var pageURLString: String -// @NSManaged public var descriptionText: String? -// @NSManaged public var publisherURLString: String? -// @NSManaged public var author: String? -// @NSManaged public var publishDate: Date? -// @NSManaged public var slug: String -// @NSManaged public var isArchived: Bool -// @NSManaged public var contentReader: String? -// @NSManaged public var labels: Set -// } - public struct FeedItem: Identifiable, Hashable { public let id: String public let title: String @@ -98,41 +70,6 @@ public struct FeedItem: Identifiable, Hashable { self.labels = labels } -// func toManagedObject(inContext context: NSManagedObjectContext) -> FeedItemManagedObject? { -// let entityName = FeedItemManagedObject.entityName -// guard let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context) else { -// print("Failed to create \(entityName)") -// return nil -// } -// -// let object = FeedItemManagedObject(entity: entityDescription, insertInto: context) -// object.id = id -// object.title = title -// object.createdAt = createdAt -// object.savedAt = savedAt -// object.readingProgress = readingProgress -// object.readingProgressAnchor = readingProgressAnchor -// object.imageURLString = imageURLString -// object.onDeviceImageURLString = onDeviceImageURLString -// object.documentDirectoryPath = documentDirectoryPath -// object.pageURLString = pageURLString -// object.descriptionText = descriptionText -// object.publisherURLString = publisherURLString -// object.author = author -// object.publishDate = publishDate -// object.slug = slug -// object.isArchived = isArchived -// object.contentReader = contentReader -// -// for label in labels { -// if let managedLabel = label.toManagedObject(inContext: context) { -// object.labels.insert(managedLabel) -// } -// } -// -// return object -// } - public static func fromJsonArticle(linkData: Data) -> FeedItem? { try? JSONDecoder().decode(JSONArticle.self, from: linkData).feedItem } diff --git a/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift b/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift index b37af81a9..eeed689b7 100644 --- a/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift +++ b/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift @@ -1,4 +1,3 @@ -// import CoreData import Foundation public struct FeedItemLabel: Decodable, Hashable { @@ -21,34 +20,4 @@ public struct FeedItemLabel: Decodable, Hashable { self.createdAt = createdAt self.labelDescription = labelDescription } - -// func toManagedObject(inContext context: NSManagedObjectContext) -> FeedItemLabelManagedObject? { -// let entityName = FeedItemLabelManagedObject.entityName -// guard let entityDescription = NSEntityDescription.entity(forEntityName: entityName, in: context) else { -// print("Failed to create \(entityName)") -// return nil -// } -// -// let object = FeedItemLabelManagedObject(entity: entityDescription, insertInto: context) -// object.id = id -// object.name = name -// object.color = color -// object.createdAt = createdAt -// object.labelDescription = labelDescription -// return object -// } } - -// public class FeedItemLabelManagedObject: NSManagedObject { -// static let entityName = "FeedItemLabelManagedObject" -// -// @nonobjc public class func fetchRequest() -> NSFetchRequest { -// NSFetchRequest(entityName: entityName) -// } -// -// @NSManaged public var id: String -// @NSManaged public var name: String -// @NSManaged public var color: String -// @NSManaged public var createdAt: Date? -// @NSManaged public var labelDescription: String? -// } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 846e337c8..b0f4e7e41 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -1,4 +1,5 @@ import Combine +import CoreData import Foundation import Models @@ -10,9 +11,6 @@ public final class DataService: ObservableObject { public internal(set) var currentViewer: Viewer? let networker: Networker - public let pageCache = NSCache() - let pageCacheQueue = DispatchQueue.global(qos: .background) - let highlightsCache = NSCache() let highlightsCacheQueue = DispatchQueue(label: "app.omnivore.highlights.cache.queue", attributes: .concurrent) @@ -53,21 +51,23 @@ public extension DataService { let slug = item.slug articleContentPublisher(username: viewer.username, slug: slug).sink( receiveCompletion: { _ in }, - receiveValue: { [weak self] articleContent in - self?.pageCache.setObject(CachedPageContent(slug, articleContent), forKey: NSString(string: slug)) - } + receiveValue: { _ in } ) .store(in: &subscriptions) } } func pageFromCache(slug: String) -> ArticleContent? { - pageCache.object(forKey: NSString(string: slug))?.value - } - - func invalidateCachedPage(slug: String?) { - if let slug = slug { - pageCache.removeObject(forKey: NSString(string: slug)) + let fetchRequest: NSFetchRequest = PersistedArticleContent.fetchRequest() + fetchRequest.predicate = NSPredicate( + format: "slug = %@", slug + ) + if let htmlContent = try? persistentContainer.viewContext.fetch(fetchRequest).first?.htmlContent { + return ArticleContent(htmlContent: htmlContent, highlights: []) + } else { + return nil } } + + func invalidateCachedPage(slug _: String?) {} } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index 8822a0d98..3218215d8 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -37,11 +37,13 @@ public extension DataService { return Deferred { Future { promise in - send(query, to: path, headers: headers) { result in + send(query, to: path, headers: headers) { [weak self] result in switch result { case let .success(payload): switch payload.data { case let .success(result: result): + // store result in core data + self?.persistArticleContent(htmlContent: result.htmlContent, slug: slug) promise(.success(result)) case .error: promise(.failure(.unknown)) @@ -56,3 +58,19 @@ public extension DataService { .eraseToAnyPublisher() } } + +extension DataService { + func persistArticleContent(htmlContent: String, slug: String) { + let persistedArticleContent = PersistedArticleContent(context: persistentContainer.viewContext) + persistedArticleContent.htmlContent = htmlContent + persistedArticleContent.slug = slug + + do { + try persistentContainer.viewContext.save() + print("PersistedArticleContent saved succesfully") + } catch { + persistentContainer.viewContext.rollback() + print("Failed to save PersistedArticleContent: \(error)") + } + } +} From bd8798512a1e79d841d0a1ccc971eb5fad3783f2 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Sat, 16 Apr 2022 14:11:37 -0700 Subject: [PATCH 07/76] add PersistedHighlight to core data model --- .../CoreDataModel.xcdatamodel/contents | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 31fc245a2..8002ea47e 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -30,9 +30,22 @@ + + + + + + + + + + + + + \ No newline at end of file From 8ea404a8d89d5e003925786a909d9fee63a3421b Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Sat, 16 Apr 2022 23:52:54 -0700 Subject: [PATCH 08/76] use coredata to track pdf highlights --- .../App/PDFSupport/PDFViewerViewModel.swift | 4 +- .../CoreDataModel.xcdatamodel/contents | 24 +++++- .../Sources/Models/Highlight.swift | 33 ++++++++ .../Services/DataService/DataService.swift | 20 ++++- .../CachedPDFHighlights.swift | 76 +++++++++---------- 5 files changed, 110 insertions(+), 47 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift index 4240b9509..d9b3c5e48 100644 --- a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift @@ -41,7 +41,7 @@ public final class PDFViewerViewModel: ObservableObject { for highlight in fetchedHighlights { resultSet[highlight.id] = highlight } - for highlightId in services.dataService.fetchRemovedHighlightIds(pdfID: feedItem.id) { + for highlightId in services.dataService.deletedHighlightsIDs { resultSet.removeValue(forKey: highlightId) } return Array(resultSet.values) @@ -164,6 +164,6 @@ public final class PDFViewerViewModel: ObservableObject { } private func removeLocalHighlights(highlightIds: [String]) { - services.dataService.removeHighlights(pdfID: feedItem.id, highlightIds: highlightIds) + services.dataService.removeHighlights(highlightIds: highlightIds) } } diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 8002ea47e..449b5cd29 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -3,6 +3,11 @@ + + + + + @@ -22,6 +27,11 @@ + + + + + @@ -29,23 +39,35 @@ + + + + + + + + + + + + - + \ No newline at end of file diff --git a/apple/OmnivoreKit/Sources/Models/Highlight.swift b/apple/OmnivoreKit/Sources/Models/Highlight.swift index 477c33352..a7d079125 100644 --- a/apple/OmnivoreKit/Sources/Models/Highlight.swift +++ b/apple/OmnivoreKit/Sources/Models/Highlight.swift @@ -1,3 +1,4 @@ +import CoreData import Foundation public struct Highlight: Identifiable, Hashable, Codable { @@ -35,4 +36,36 @@ public struct Highlight: Identifiable, Hashable, Codable { self.updatedAt = updatedAt self.createdByMe = createdByMe } + + public func toManagedObject(context: NSManagedObjectContext, associatedItemID: String) -> PersistedHighlight { + let persistedHighlight = PersistedHighlight(context: context) + persistedHighlight.associatedItemId = associatedItemID + persistedHighlight.markedForDeletion = false + persistedHighlight.id = id + persistedHighlight.shortId = shortId + persistedHighlight.quote = quote + persistedHighlight.prefix = prefix + persistedHighlight.suffix = suffix + persistedHighlight.patch = patch + persistedHighlight.annotation = annotation + persistedHighlight.createdAt = createdAt + persistedHighlight.updatedAt = updatedAt + persistedHighlight.createdByMe = createdByMe + return persistedHighlight + } + + public static func make(from persistedHighlight: PersistedHighlight) -> Highlight { + Highlight( + id: persistedHighlight.id ?? "", + shortId: persistedHighlight.shortId ?? "", + quote: persistedHighlight.quote ?? "", + prefix: persistedHighlight.prefix, + suffix: persistedHighlight.suffix, + patch: persistedHighlight.patch ?? "", + annotation: persistedHighlight.annotation, + createdByMe: persistedHighlight.createdByMe, + createdAt: persistedHighlight.createdAt, + updatedAt: persistedHighlight.updatedAt + ) + } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index b0f4e7e41..83fa20999 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -11,11 +11,9 @@ public final class DataService: ObservableObject { public internal(set) var currentViewer: Viewer? let networker: Networker - let highlightsCache = NSCache() - let highlightsCacheQueue = DispatchQueue(label: "app.omnivore.highlights.cache.queue", attributes: .concurrent) - let persistentContainer: PersistentContainer var subscriptions = Set() + public var deletedHighlightsIDs = Set() public init(appEnvironment: AppEnvironment, networker: Networker) { self.appEnvironment = appEnvironment @@ -30,7 +28,21 @@ public final class DataService: ObservableObject { } public func clearHighlights() { - highlightsCache.removeAllObjects() + deletedHighlightsIDs.removeAll() + + let fetchRequest: NSFetchRequest = PersistedHighlight.fetchRequest() + + let highlights = (try? persistentContainer.viewContext.fetch(fetchRequest)) ?? [] + + for highlight in highlights { + persistentContainer.viewContext.delete(highlight) + } + + do { + try persistentContainer.viewContext.save() + } catch { + print("failed to delete objects") + } } public func switchAppEnvironment(appEnvironment: AppEnvironment) { diff --git a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift index 35ece139b..c9a225c11 100644 --- a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift +++ b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift @@ -1,57 +1,53 @@ import Combine +import CoreData import Foundation import Models -final class CachedPDFHighlights { - init(pdfID: String, highlights: [Highlight], removedHighlightIDs: [String]) { - self.pdfID = pdfID - self.highlights = highlights - self.removedHighlightIDs = removedHighlightIDs - } - - let pdfID: String - var highlights: [Highlight] - var removedHighlightIDs: [String] -} - public extension DataService { func cachedHighlights(pdfID: String) -> [Highlight] { - fetchCachedHighlights(pdfID: pdfID as NSString)?.highlights ?? [] - } + let fetchRequest: NSFetchRequest = PersistedHighlight.fetchRequest() + fetchRequest.predicate = NSPredicate( + format: "associatedItemId = %@ AND markedForDeletion = %@", pdfID, false + ) - func fetchRemovedHighlightIds(pdfID: String) -> [String] { - fetchCachedHighlights(pdfID: pdfID as NSString)?.removedHighlightIDs ?? [] + let highlights = (try? persistentContainer.viewContext.fetch(fetchRequest)) ?? [] + return highlights.map { Highlight.make(from: $0) } } func persistHighlight(pdfID: String, highlight: Highlight) { - let cachedHighlights = - fetchCachedHighlights(pdfID: pdfID as NSString) - ?? CachedPDFHighlights(pdfID: pdfID, highlights: [], removedHighlightIDs: []) + _ = highlight.toManagedObject( + context: persistentContainer.viewContext, + associatedItemID: pdfID + ) - cachedHighlights.highlights.append(highlight) - insertCachedHighlights(highlights: cachedHighlights, pdfID: pdfID as NSString) - } - - func removeHighlights(pdfID: String, highlightIds: [String]) { - let cachedHighlights = - fetchCachedHighlights(pdfID: pdfID as NSString) - ?? CachedPDFHighlights(pdfID: pdfID, highlights: [], removedHighlightIDs: []) - - cachedHighlights.removedHighlightIDs.append(contentsOf: highlightIds) - insertCachedHighlights(highlights: cachedHighlights, pdfID: pdfID as NSString) - } - - private func fetchCachedHighlights(pdfID: NSString) -> CachedPDFHighlights? { - var cachedHighlights: CachedPDFHighlights? - highlightsCacheQueue.sync { - cachedHighlights = highlightsCache.object(forKey: pdfID as NSString) + do { + try persistentContainer.viewContext.save() + print("PersistedHighlight saved succesfully") + } catch { + persistentContainer.viewContext.rollback() + print("Failed to save PersistedHighlight: \(error)") } - return cachedHighlights } - private func insertCachedHighlights(highlights: CachedPDFHighlights, pdfID: NSString) { - highlightsCacheQueue.async(flags: .barrier) { - self.highlightsCache.setObject(highlights, forKey: pdfID as AnyObject) + func removeHighlights(highlightIds: [String]) { + for highlightID in highlightIds { + deletedHighlightsIDs.insert(highlightID) + } + + let fetchRequest: NSFetchRequest = PersistedHighlight.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id IN %@", highlightIds) + guard let highlights = try? persistentContainer.viewContext.fetch(fetchRequest) else { return } + + for highlight in highlights { + highlight.markedForDeletion = true + } + + do { + try persistentContainer.viewContext.save() + print("PersistedHighlight(s) updated succesfully") + } catch { + persistentContainer.viewContext.rollback() + print("Failed to update PersistedHighlight(s): \(error)") } } } From 290894ed9b7339b4937f903eb1ee1dcd50a667c5 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Sun, 17 Apr 2022 00:04:06 -0700 Subject: [PATCH 09/76] move files --- .../Models/{ => DataModels}/ArticleContent.swift | 10 ---------- .../Sources/Models/{ => DataModels}/FeedItem.swift | 0 .../Models/{ => DataModels}/FeedItemLabel.swift | 0 .../Sources/Models/{ => DataModels}/Highlight.swift | 0 .../Models/{ => DataModels}/NewsletterEmail.swift | 0 .../Sources/Models/{ => DataModels}/UserProfile.swift | 0 6 files changed, 10 deletions(-) rename apple/OmnivoreKit/Sources/Models/{ => DataModels}/ArticleContent.swift (69%) rename apple/OmnivoreKit/Sources/Models/{ => DataModels}/FeedItem.swift (100%) rename apple/OmnivoreKit/Sources/Models/{ => DataModels}/FeedItemLabel.swift (100%) rename apple/OmnivoreKit/Sources/Models/{ => DataModels}/Highlight.swift (100%) rename apple/OmnivoreKit/Sources/Models/{ => DataModels}/NewsletterEmail.swift (100%) rename apple/OmnivoreKit/Sources/Models/{ => DataModels}/UserProfile.swift (100%) diff --git a/apple/OmnivoreKit/Sources/Models/ArticleContent.swift b/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContent.swift similarity index 69% rename from apple/OmnivoreKit/Sources/Models/ArticleContent.swift rename to apple/OmnivoreKit/Sources/Models/DataModels/ArticleContent.swift index 032ccf434..bc55cf570 100644 --- a/apple/OmnivoreKit/Sources/Models/ArticleContent.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContent.swift @@ -1,15 +1,5 @@ import Foundation -public class CachedPageContent: NSObject { - public let slug: String - public let value: ArticleContent - - public init(_ slug: String, _ content: ArticleContent) { - self.slug = slug - self.value = content - } -} - public struct ArticleContent { public let htmlContent: String public let highlights: [Highlight] diff --git a/apple/OmnivoreKit/Sources/Models/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift similarity index 100% rename from apple/OmnivoreKit/Sources/Models/FeedItem.swift rename to apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift diff --git a/apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItemLabel.swift similarity index 100% rename from apple/OmnivoreKit/Sources/Models/FeedItemLabel.swift rename to apple/OmnivoreKit/Sources/Models/DataModels/FeedItemLabel.swift diff --git a/apple/OmnivoreKit/Sources/Models/Highlight.swift b/apple/OmnivoreKit/Sources/Models/DataModels/Highlight.swift similarity index 100% rename from apple/OmnivoreKit/Sources/Models/Highlight.swift rename to apple/OmnivoreKit/Sources/Models/DataModels/Highlight.swift diff --git a/apple/OmnivoreKit/Sources/Models/NewsletterEmail.swift b/apple/OmnivoreKit/Sources/Models/DataModels/NewsletterEmail.swift similarity index 100% rename from apple/OmnivoreKit/Sources/Models/NewsletterEmail.swift rename to apple/OmnivoreKit/Sources/Models/DataModels/NewsletterEmail.swift diff --git a/apple/OmnivoreKit/Sources/Models/UserProfile.swift b/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift similarity index 100% rename from apple/OmnivoreKit/Sources/Models/UserProfile.swift rename to apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift From 1d92e64801a8773809ce29945067c1810fc90451 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 18 Apr 2022 13:44:52 -0700 Subject: [PATCH 10/76] define viewer as a nsmanagedobject --- .../App/PDFSupport/PDFViewerViewModel.swift | 4 +- .../App/Views/LinkItemDetailView.swift | 2 +- .../App/Views/Profile/ProfileView.swift | 4 +- .../App/Views/RootView/RootViewModel.swift | 4 +- .../Views/WebReader/WebReaderViewModel.swift | 4 +- .../CoreDataModel.xcdatamodel/contents | 16 +++++- .../Models/CoreData/StorageProvider.swift | 10 +++- .../Models/DataModels/UserProfile.swift | 19 ------- .../Services/DataService/DataService.swift | 14 +++-- .../Queries/LibraryItemsQuery.swift | 2 +- .../DataService/Queries/ViewerFetcher.swift | 57 +++++++++++++++---- 11 files changed, 88 insertions(+), 48 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift index d9b3c5e48..9512dc7d0 100644 --- a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift @@ -18,9 +18,9 @@ public final class PDFViewerViewModel: ObservableObject { } public func loadHighlights(completion onComplete: @escaping ([Highlight]) -> Void) { - guard let viewer = services.dataService.currentViewer else { return } + guard let username = services.dataService.currentViewer?.username else { return } - services.dataService.pdfHighlightsPublisher(username: viewer.username, slug: feedItem.slug).sink( + services.dataService.pdfHighlightsPublisher(username: username, slug: feedItem.slug).sink( receiveCompletion: { [weak self] completion in guard case .failure = completion else { return } onComplete(self?.allHighlights(fetchedHighlights: []) ?? []) diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index 6ae0f7ad9..4d677e53f 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -56,7 +56,7 @@ enum PDFProvider { if let viewer = viewer { createWebAppWrapperViewModel( - username: viewer.username, + username: viewer.username ?? "", dataService: dataService, rawAuthCookie: rawAuthCookie ) diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift index aab2f65ab..84dd050e9 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift @@ -21,8 +21,8 @@ import Views guard let viewer = try? await dataService.fetchViewer() else { return } profileCardData = ProfileCardData( - name: viewer.name, - username: viewer.username, + name: viewer.name ?? "", + username: viewer.username ?? "", imageURL: viewer.profileImageURL.flatMap { URL(string: $0) } ) } diff --git a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift index a8afdf799..9def7c1df 100644 --- a/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/RootView/RootViewModel.swift @@ -64,8 +64,8 @@ public final class RootViewModel: ObservableObject { return } - if let viewer = try? await services.dataService.fetchViewer() { - let path = linkRequestPath(username: viewer.username, requestID: linkRequestID) + if let username = try? await services.dataService.fetchViewer().username { + let path = linkRequestPath(username: username, requestID: linkRequestID) webLinkPath = SafariWebLinkPath(id: UUID(), path: path) } } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 081311164..4b7b8f855 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -25,14 +25,14 @@ final class WebReaderViewModel: ObservableObject { self.slug = slug isLoading = true - guard let viewer = dataService.currentViewer else { return } + guard let username = dataService.currentViewer?.username else { return } if let content = dataService.pageFromCache(slug: slug) { articleContent = content // continue to load from the web if possible } - dataService.articleContentPublisher(username: viewer.username, slug: slug).sink( + dataService.articleContentPublisher(username: username, slug: slug).sink( receiveCompletion: { [weak self] completion in guard case .failure = completion else { return } self?.isLoading = false diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 449b5cd29..655f7476c 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -64,10 +64,22 @@ + + + + + + + + + + + - - + + + \ No newline at end of file diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift index 1f42124dd..8641865ef 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift +++ b/apple/OmnivoreKit/Sources/Models/CoreData/StorageProvider.swift @@ -7,6 +7,14 @@ public class PersistentContainer: NSPersistentContainer { public static func make() -> PersistentContainer { let modelURL = Bundle.module.url(forResource: "CoreDataModel", withExtension: "momd")! let model = NSManagedObjectModel(contentsOf: modelURL)! - return PersistentContainer(name: "DataModel", managedObjectModel: model) + let container = PersistentContainer(name: "DataModel", managedObjectModel: model) + + container.viewContext.automaticallyMergesChangesFromParent = false + container.viewContext.name = "viewContext" + container.viewContext.mergePolicy = NSMergeByPropertyObjectTrumpMergePolicy + container.viewContext.undoManager = nil + container.viewContext.shouldDeleteInaccessibleFaults = true + + return container } } diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift b/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift index d3588df5c..85388588b 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift @@ -44,22 +44,3 @@ public extension UserProfile { return nil } } - -public struct Viewer { - public let userID: String - public let username: String - public let name: String - public let profileImageURL: String? - - public init( - username: String, - name: String, - profileImageURL: String?, - userID: String - ) { - self.username = username - self.name = name - self.profileImageURL = profileImageURL - self.userID = userID - } -} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 83fa20999..a3bfe05d2 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -2,14 +2,15 @@ import Combine import CoreData import Foundation import Models +import OSLog public final class DataService: ObservableObject { public static var registerIntercomUser: ((String) -> Void)? public static var showIntercomMessenger: (() -> Void)? public let appEnvironment: AppEnvironment - public internal(set) var currentViewer: Viewer? let networker: Networker + static let logger = Logger(subsystem: "app.omnivore", category: "data-service") let persistentContainer: PersistentContainer var subscriptions = Set() @@ -27,6 +28,11 @@ public final class DataService: ObservableObject { } } + public var currentViewer: Viewer? { + let fetchRequest: NSFetchRequest = Viewer.fetchRequest() + return try? persistentContainer.viewContext.fetch(fetchRequest).first + } + public func clearHighlights() { deletedHighlightsIDs.removeAll() @@ -41,7 +47,7 @@ public final class DataService: ObservableObject { do { try persistentContainer.viewContext.save() } catch { - print("failed to delete objects") + DataService.logger.debug("failed to delete objects") } } @@ -57,11 +63,11 @@ public final class DataService: ObservableObject { public extension DataService { func prefetchPages(items: [FeedItem]) { - guard let viewer = currentViewer else { return } + guard let username = currentViewer?.username else { return } for item in items { let slug = item.slug - articleContentPublisher(username: viewer.username, slug: slug).sink( + articleContentPublisher(username: username, slug: slug).sink( receiveCompletion: { _ in }, receiveValue: { _ in } ) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift index f2aaa530d..d6f31e93c 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift @@ -6,7 +6,7 @@ import SwiftGraphQL public extension DataService { func articlePublisher(slug: String) -> AnyPublisher { internalViewerPublisher() - .flatMap { self.internalArticlePublisher(username: $0.username, slug: slug) } + .flatMap { self.internalArticlePublisher(username: $0.username ?? "", slug: slug) } .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift index 509ff01a7..f526c23f3 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift @@ -1,4 +1,5 @@ import Combine +import CoreData import Foundation import Models import SwiftGraphQL @@ -6,16 +7,16 @@ import Utils public extension DataService { func fetchViewer() async throws -> Viewer { - let selection = Selection { - Viewer( + let selection = Selection { + ViewerInternal( + userID: try $0.id(), username: try $0.profile( selection: .init { try $0.username() } ), name: try $0.name(), profileImageURL: try $0.profile( selection: .init { try $0.pictureUrl() } - ), - userID: try $0.id() + ) ) } @@ -30,12 +31,16 @@ public extension DataService { send(query, to: path, headers: headers) { [weak self] result in switch result { case let .success(payload): - self?.currentViewer = payload.data if UserDefaults.standard.string(forKey: Keys.userIdKey) == nil { UserDefaults.standard.setValue(payload.data.userID, forKey: Keys.userIdKey) DataService.registerIntercomUser?(payload.data.userID) } - continuation.resume(returning: payload.data) + + if let self = self, let viewer = payload.data.persist(context: self.persistentContainer.viewContext) { + continuation.resume(returning: viewer) + } else { + continuation.resume(throwing: BasicError.message(messageText: "coredata error")) + } case .failure: continuation.resume(throwing: BasicError.message(messageText: "http error")) } @@ -47,16 +52,16 @@ public extension DataService { extension DataService { @available(*, deprecated, message: "use async version instead") func internalViewerPublisher() -> AnyPublisher { - let selection = Selection { - Viewer( + let selection = Selection { + ViewerInternal( + userID: try $0.id(), username: try $0.profile( selection: .init { try $0.username() } ), name: try $0.name(), profileImageURL: try $0.profile( selection: .init { try $0.pictureUrl() } - ), - userID: try $0.id() + ) ) } @@ -72,8 +77,11 @@ extension DataService { send(query, to: path, headers: headers) { result in switch result { case let .success(payload): - self?.currentViewer = payload.data - promise(.success(payload.data)) + if let self = self, let viewer = payload.data.persist(context: self.persistentContainer.viewContext) { + promise(.success(viewer)) + } else { + promise(.failure(.message(messageText: "coredata error"))) + } case .failure: promise(.failure(.message(messageText: "http error"))) } @@ -83,3 +91,28 @@ extension DataService { .eraseToAnyPublisher() } } + +private struct ViewerInternal { + let userID: String + let username: String + let name: String + let profileImageURL: String? + + func persist(context: NSManagedObjectContext) -> Viewer? { + let viewer = Viewer(context: context) + viewer.userID = userID + viewer.username = username + viewer.name = name + viewer.profileImageURL = profileImageURL + + do { + try context.save() + DataService.logger.debug("Viewer saved succesfully") + return viewer + } catch { + context.rollback() + DataService.logger.debug("Failed to save Viewer: \(error.localizedDescription)") + return nil + } + } +} From 905e628e5a0c132387cdf54276195caab6c27ba9 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 18 Apr 2022 15:30:16 -0700 Subject: [PATCH 11/76] create helper function to get unwrapped viewer properties --- .../Sources/App/PDFSupport/PDFViewerViewModel.swift | 4 ++-- .../Sources/App/Views/LinkItemDetailView.swift | 2 +- .../Sources/App/Views/Profile/ProfileView.swift | 4 ++-- .../Sources/Models/DataModels/UserProfile.swift | 10 ++++++++++ .../Sources/Services/DataService/DataService.swift | 1 + 5 files changed, 16 insertions(+), 5 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift index 9512dc7d0..7d1048eef 100644 --- a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift @@ -154,8 +154,8 @@ public final class PDFViewerViewModel: ObservableObject { let baseURL = services.dataService.appEnvironment.serverBaseURL var components = URLComponents(url: baseURL, resolvingAgainstBaseURL: false) - if let viewer = services.dataService.currentViewer?.username { - components?.path = "/\(viewer)/\(feedItem.slug)/highlights/\(shortId)" + if let username = services.dataService.currentViewer?.username { + components?.path = "/\(username)/\(feedItem.slug)/highlights/\(shortId)" } else { return nil } diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index 4d677e53f..a3de5f6a8 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -56,7 +56,7 @@ enum PDFProvider { if let viewer = viewer { createWebAppWrapperViewModel( - username: viewer.username ?? "", + username: viewer.unwrappedUsername, dataService: dataService, rawAuthCookie: rawAuthCookie ) diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift index 84dd050e9..5727d96d2 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/ProfileView.swift @@ -21,8 +21,8 @@ import Views guard let viewer = try? await dataService.fetchViewer() else { return } profileCardData = ProfileCardData( - name: viewer.name ?? "", - username: viewer.username ?? "", + name: viewer.unwrappedName, + username: viewer.unwrappedUsername, imageURL: viewer.profileImageURL.flatMap { URL(string: $0) } ) } diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift b/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift index 85388588b..2c478a27e 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/UserProfile.swift @@ -44,3 +44,13 @@ public extension UserProfile { return nil } } + +public extension Viewer { + var unwrappedUsername: String { + username ?? "" + } + + var unwrappedName: String { + name ?? "" + } +} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index a3bfe05d2..57287283b 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -30,6 +30,7 @@ public final class DataService: ObservableObject { public var currentViewer: Viewer? { let fetchRequest: NSFetchRequest = Viewer.fetchRequest() + fetchRequest.fetchLimit = 1 // we should only have one viewer saved return try? persistentContainer.viewContext.fetch(fetchRequest).first } From 05d4107fa974f0bf69d9f12ae7cd1469dc5f8c5c Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 18 Apr 2022 15:56:12 -0700 Subject: [PATCH 12/76] use coredata for newsletter emails --- .../Views/Profile/NewsletterEmailsView.swift | 2 +- .../CoreDataModel.xcdatamodel/contents | 11 +++++ .../Models/DataModels/NewsletterEmail.swift | 15 +++--- .../CreateNewsletterEmailMutation.swift | 11 +++-- .../Queries/NewsletterEmailsQuery.swift | 10 ++-- .../InternalNewsletterEmail.swift | 47 +++++++++++++++++++ 6 files changed, 80 insertions(+), 16 deletions(-) create mode 100644 apple/OmnivoreKit/Sources/Services/InternalModels/InternalNewsletterEmail.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift b/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift index 5f171b349..da89450fa 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Profile/NewsletterEmailsView.swift @@ -97,7 +97,7 @@ struct NewsletterEmailsView: View { Snackbar.show(message: "Email copied") }, - label: { Text(newsletterEmail.email) } + label: { Text(newsletterEmail.unwrappedEmail) } ) } } diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 655f7476c..2a18d1709 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -1,5 +1,15 @@ + + + + + + + + + + @@ -81,5 +91,6 @@ + \ No newline at end of file diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/NewsletterEmail.swift b/apple/OmnivoreKit/Sources/Models/DataModels/NewsletterEmail.swift index ee4270668..5a7f540ba 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/NewsletterEmail.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/NewsletterEmail.swift @@ -1,14 +1,11 @@ import Foundation -public struct NewsletterEmail: Identifiable { - public let id = UUID() - public let emailId: String - public let email: String - public let confirmationCode: String? +public extension NewsletterEmail { + var unwrappedEmailId: String { + emailId ?? "" + } - public init(emailId: String, email: String, confirmationCode: String?) { - self.emailId = emailId - self.email = email - self.confirmationCode = confirmationCode + var unwrappedEmail: String { + email ?? "" } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateNewsletterEmailMutation.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateNewsletterEmailMutation.swift index b8028e47e..38b9cd6de 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateNewsletterEmailMutation.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateNewsletterEmailMutation.swift @@ -1,4 +1,5 @@ import Combine +import CoreData import Foundation import Models import SwiftGraphQL @@ -6,7 +7,7 @@ import SwiftGraphQL public extension DataService { func createNewsletterEmailPublisher() -> AnyPublisher { enum MutationResult { - case saved(newsletterEmail: NewsletterEmail) + case saved(newsletterEmail: InternalNewsletterEmail) case error(errorCode: Enums.CreateNewsletterEmailErrorCode) } @@ -14,7 +15,7 @@ public extension DataService { try $0.on( createNewsletterEmailSuccess: .init { .saved(newsletterEmail: try $0.newsletterEmail(selection: Selection.NewsletterEmail { - NewsletterEmail( + InternalNewsletterEmail( emailId: try $0.id(), email: try $0.address(), confirmationCode: try $0.confirmationCode() @@ -44,7 +45,11 @@ public extension DataService { switch payload.data { case let .saved(newsletterEmail: newsletterEmail): - promise(.success(newsletterEmail)) + if let newsletterEmailObject = newsletterEmail.persist(context: self.persistentContainer.viewContext) { + promise(.success(newsletterEmailObject)) + } else { + promise(.failure(.message(messageText: "coredata error"))) + } case let .error(errorCode: errorCode): promise(.failure(.message(messageText: errorCode.rawValue))) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/NewsletterEmailsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/NewsletterEmailsQuery.swift index 25ccea69f..47afa350c 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/NewsletterEmailsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/NewsletterEmailsQuery.swift @@ -6,12 +6,12 @@ import SwiftGraphQL public extension DataService { func newsletterEmailsPublisher() -> AnyPublisher<[NewsletterEmail], ServerError> { enum QueryResult { - case success(result: [NewsletterEmail]) + case success(result: [InternalNewsletterEmail]) case error(error: String) } let newsletterEmailSelection = Selection.NewsletterEmail { - NewsletterEmail( + InternalNewsletterEmail( emailId: try $0.id(), email: try $0.address(), confirmationCode: try $0.confirmationCode() @@ -43,7 +43,11 @@ public extension DataService { case let .success(payload): switch payload.data { case let .success(result: result): - promise(.success(result)) + if let newsletterEmailObject = result.persist(context: self.persistentContainer.viewContext) { + promise(.success(newsletterEmailObject)) + } else { + promise(.failure(.unknown)) + } case .error: promise(.failure(.unknown)) } diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalNewsletterEmail.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalNewsletterEmail.swift new file mode 100644 index 000000000..cd5dd3329 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalNewsletterEmail.swift @@ -0,0 +1,47 @@ +import CoreData +import Foundation +import Models + +struct InternalNewsletterEmail { + let emailId: String + let email: String + let confirmationCode: String? + + func persist(context: NSManagedObjectContext) -> NewsletterEmail? { + let newsletterEmail = asManagedObject(inContext: context) + + do { + try context.save() + DataService.logger.debug("NewsletterEmail saved succesfully") + return newsletterEmail + } catch { + context.rollback() + DataService.logger.debug("Failed to save NewsletterEmail: \(error.localizedDescription)") + return nil + } + } + + func asManagedObject(inContext context: NSManagedObjectContext) -> NewsletterEmail { + let newsletterEmail = NewsletterEmail(context: context) + newsletterEmail.emailId = emailId + newsletterEmail.email = email + newsletterEmail.confirmationCode = confirmationCode + return newsletterEmail + } +} + +extension Sequence where Element == InternalNewsletterEmail { + func persist(context: NSManagedObjectContext) -> [NewsletterEmail]? { + let newsletterEmails = map { $0.asManagedObject(inContext: context) } + + do { + try context.save() + DataService.logger.debug("NewsletterEmail saved succesfully") + return newsletterEmails + } catch { + context.rollback() + DataService.logger.debug("Failed to save NewsletterEmail: \(error.localizedDescription)") + return nil + } + } +} From a60a5e3991f64aeeed8b7b4369ee3b1d13e5d67a Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 18 Apr 2022 17:03:34 -0700 Subject: [PATCH 13/76] mark some models as deprecated --- .../App/PDFSupport/PDFViewerViewModel.swift | 10 +++---- .../App/Views/Home/HomeFeedViewModel.swift | 6 ++-- .../App/Views/Labels/ApplyLabelsView.swift | 8 +++--- .../App/Views/Labels/LabelsViewModel.swift | 14 +++++----- .../App/Views/WebReader/WebReader.swift | 2 +- .../Views/WebReader/WebReaderContent.swift | 4 +-- .../Views/WebReader/WebReaderViewModel.swift | 4 +-- .../CoreDataModel.xcdatamodel/contents | 28 +++++++++---------- ...eContent.swift => ArticleContentDep.swift} | 6 ++-- .../Sources/Models/DataModels/FeedItem.swift | 4 +-- ...ItemLabel.swift => FeedItemLabelDep.swift} | 2 +- .../{Highlight.swift => HighlightDep.swift} | 6 ++-- .../Services/DataService/DataService.swift | 4 +-- .../Mutations/CreateHighlight.swift | 4 +-- .../Mutations/CreateLabelPublisher.swift | 4 +-- .../Mutations/MergeHighlight.swift | 4 +-- .../UpdateArticleLabelsPublisher.swift | 4 +-- .../Mutations/UpdateHighlightAttributes.swift | 4 +-- .../Queries/ArticleContentQuery.swift | 6 ++-- .../DataService/Queries/LabelsPublisher.swift | 4 +-- .../Queries/PDFHighlightsQuery.swift | 6 ++-- .../Selections/FeedItemLabelSelection.swift | 2 +- .../Selections/HighlightSelection.swift | 2 +- .../CachedPDFHighlights.swift | 6 ++-- .../OmnivoreKit/Sources/Views/TextChip.swift | 4 +-- 25 files changed, 74 insertions(+), 74 deletions(-) rename apple/OmnivoreKit/Sources/Models/DataModels/{ArticleContent.swift => ArticleContentDep.swift} (78%) rename apple/OmnivoreKit/Sources/Models/DataModels/{FeedItemLabel.swift => FeedItemLabelDep.swift} (88%) rename apple/OmnivoreKit/Sources/Models/DataModels/{Highlight.swift => HighlightDep.swift} (95%) diff --git a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift index 7d1048eef..c1fde6ec3 100644 --- a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift @@ -17,7 +17,7 @@ public final class PDFViewerViewModel: ObservableObject { self.feedItem = feedItem } - public func loadHighlights(completion onComplete: @escaping ([Highlight]) -> Void) { + public func loadHighlights(completion onComplete: @escaping ([HighlightDep]) -> Void) { guard let username = services.dataService.currentViewer?.username else { return } services.dataService.pdfHighlightsPublisher(username: username, slug: feedItem.slug).sink( @@ -32,8 +32,8 @@ public final class PDFViewerViewModel: ObservableObject { .store(in: &subscriptions) } - private func allHighlights(fetchedHighlights: [Highlight]) -> [Highlight] { - var resultSet = [String: Highlight]() + private func allHighlights(fetchedHighlights: [HighlightDep]) -> [HighlightDep] { + var resultSet = [String: HighlightDep]() for highlight in services.dataService.cachedHighlights(pdfID: feedItem.id) { resultSet[highlight.id] = highlight @@ -50,7 +50,7 @@ public final class PDFViewerViewModel: ObservableObject { public func createHighlight(shortId: String, highlightID: String, quote: String, patch: String) { services.dataService.persistHighlight( pdfID: feedItem.id, - highlight: Highlight( + highlight: HighlightDep( id: highlightID, shortId: shortId, quote: quote, @@ -88,7 +88,7 @@ public final class PDFViewerViewModel: ObservableObject { ) { services.dataService.persistHighlight( pdfID: feedItem.id, - highlight: Highlight( + highlight: HighlightDep( id: highlightID, shortId: shortId, quote: quote, diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index 0460a1578..d2d052fb8 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -12,14 +12,14 @@ import Views var uncommittedReadingProgressUpdates = [String: Double]() /// Track label updates to be committed when user navigates back to grid view - var uncommittedLabelUpdates = [String: [FeedItemLabel]]() + var uncommittedLabelUpdates = [String: [FeedItemLabelDep]]() @Published var items = [FeedItem]() @Published var isLoading = false @Published var showPushNotificationPrimer = false @Published var itemUnderLabelEdit: FeedItem? @Published var searchTerm = "" - @Published var selectedLabels = [FeedItemLabel]() + @Published var selectedLabels = [FeedItemLabelDep]() @Published var snoozePresented = false @Published var itemToSnooze: FeedItem? @Published var selectedLinkItem: FeedItem? @@ -194,7 +194,7 @@ import Views } } - func updateLabels(itemID: String, labels: [FeedItemLabel]) { + func updateLabels(itemID: String, labels: [FeedItemLabelDep]) { // If item is being being displayed then delay the state update of labels until // user is no longer reading the item. if selectedLinkItem != nil { diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift index 5c81b1b70..8e2fa47f1 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift @@ -6,7 +6,7 @@ import Views struct ApplyLabelsView: View { enum Mode { case item(FeedItem) - case list([FeedItemLabel]) + case list([FeedItemLabelDep]) var navTitle: String { switch self { @@ -28,7 +28,7 @@ struct ApplyLabelsView: View { } let mode: Mode - let commitLabelChanges: ([FeedItemLabel]) -> Void + let commitLabelChanges: ([FeedItemLabelDep]) -> Void @EnvironmentObject var dataService: DataService @Environment(\.presentationMode) private var presentationMode @@ -147,8 +147,8 @@ struct ApplyLabelsView: View { } } -private extension Sequence where Element == FeedItemLabel { - func applySearchFilter(_ searchFilter: String) -> [FeedItemLabel] { +private extension Sequence where Element == FeedItemLabelDep { + func applySearchFilter(_ searchFilter: String) -> [FeedItemLabelDep] { if searchFilter.isEmpty { return map { $0 } // return the identity of the sequence } diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift index 35159e8ca..d35f3c9d7 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift @@ -7,9 +7,9 @@ import Views final class LabelsViewModel: ObservableObject { private var hasLoadedInitialLabels = false @Published var isLoading = false - @Published var selectedLabels = [FeedItemLabel]() - @Published var unselectedLabels = [FeedItemLabel]() - @Published var labels = [FeedItemLabel]() + @Published var selectedLabels = [FeedItemLabelDep]() + @Published var unselectedLabels = [FeedItemLabelDep]() + @Published var labels = [FeedItemLabelDep]() @Published var showCreateEmailModal = false var subscriptions = Set() @@ -19,7 +19,7 @@ final class LabelsViewModel: ObservableObject { /// - dataService: `DataService` reference /// - item: Optional `FeedItem` for applying labels to a single item /// - initiallySelectedLabels: Optional `[FeedItemLabel]` for filtering a list of items - func loadLabels(dataService: DataService, item: FeedItem? = nil, initiallySelectedLabels: [FeedItemLabel]? = nil) { + func loadLabels(dataService: DataService, item: FeedItem? = nil, initiallySelectedLabels: [FeedItemLabelDep]? = nil) { guard !hasLoadedInitialLabels else { return } isLoading = true @@ -82,7 +82,7 @@ final class LabelsViewModel: ObservableObject { .store(in: &subscriptions) } - func saveItemLabelChanges(itemID: String, dataService: DataService, onComplete: @escaping ([FeedItemLabel]) -> Void) { + func saveItemLabelChanges(itemID: String, dataService: DataService, onComplete: @escaping ([FeedItemLabelDep]) -> Void) { isLoading = true dataService.updateArticleLabelsPublisher(itemID: itemID, labelIDs: selectedLabels.map(\.id)).sink( receiveCompletion: { [weak self] _ in @@ -93,12 +93,12 @@ final class LabelsViewModel: ObservableObject { .store(in: &subscriptions) } - func addLabelToItem(_ label: FeedItemLabel) { + func addLabelToItem(_ label: FeedItemLabelDep) { selectedLabels.insert(label, at: 0) unselectedLabels.removeAll { $0.id == label.id } } - func removeLabelFromItem(_ label: FeedItemLabel) { + func removeLabelFromItem(_ label: FeedItemLabelDep) { unselectedLabels.insert(label, at: 0) selectedLabels.removeAll { $0.id == label.id } } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index a4544fafb..c7c678c2e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -6,7 +6,7 @@ import WebKit #if os(iOS) struct WebReader: UIViewRepresentable { - let articleContent: ArticleContent + let articleContent: ArticleContentDep let item: FeedItem let openLinkAction: (URL) -> Void let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift index 1f0b813ea..8414c374d 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift @@ -4,12 +4,12 @@ import Utils struct WebReaderContent { let textFontSize: Int - let articleContent: ArticleContent + let articleContent: ArticleContentDep let item: FeedItem let themeKey: String init( - articleContent: ArticleContent, + articleContent: ArticleContentDep, item: FeedItem, isDark: Bool, fontSize: Int diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 4b7b8f855..3895f6b35 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -9,14 +9,14 @@ struct SafariWebLink: Identifiable { let url: URL } -func encodeHighlightResult(_ highlight: Highlight) -> [String: Any]? { +func encodeHighlightResult(_ highlight: HighlightDep) -> [String: Any]? { guard let data = try? JSONEncoder().encode(highlight) else { return nil } return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] } final class WebReaderViewModel: ObservableObject { @Published var isLoading = false - @Published var articleContent: ArticleContent? + @Published var articleContent: ArticleContentDep? var slug: String? var subscriptions = Set() diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 2a18d1709..568d9d5e3 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -1,5 +1,17 @@ + + + + + + + + + + + + @@ -36,19 +48,7 @@ - - - - - - - - - - - - - + @@ -88,7 +88,7 @@ - + diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContent.swift b/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContentDep.swift similarity index 78% rename from apple/OmnivoreKit/Sources/Models/DataModels/ArticleContent.swift rename to apple/OmnivoreKit/Sources/Models/DataModels/ArticleContentDep.swift index bc55cf570..101900231 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContent.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContentDep.swift @@ -1,12 +1,12 @@ import Foundation -public struct ArticleContent { +public struct ArticleContentDep { public let htmlContent: String - public let highlights: [Highlight] + public let highlights: [HighlightDep] public init( htmlContent: String, - highlights: [Highlight] + highlights: [HighlightDep] ) { self.htmlContent = htmlContent self.highlights = highlights diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift index 3a2b3ea83..f7618d6bc 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift @@ -28,7 +28,7 @@ public struct FeedItem: Identifiable, Hashable { public let slug: String public let isArchived: Bool public let contentReader: String? - public var labels: [FeedItemLabel] + public var labels: [FeedItemLabelDep] public init( id: String, @@ -48,7 +48,7 @@ public struct FeedItem: Identifiable, Hashable { slug: String, isArchived: Bool, contentReader: String?, - labels: [FeedItemLabel] + labels: [FeedItemLabelDep] ) { self.id = id self.title = title diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItemLabel.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItemLabelDep.swift similarity index 88% rename from apple/OmnivoreKit/Sources/Models/DataModels/FeedItemLabel.swift rename to apple/OmnivoreKit/Sources/Models/DataModels/FeedItemLabelDep.swift index eeed689b7..ef40ae284 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItemLabel.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItemLabelDep.swift @@ -1,6 +1,6 @@ import Foundation -public struct FeedItemLabel: Decodable, Hashable { +public struct FeedItemLabelDep: Decodable, Hashable { public let id: String public let name: String public let color: String diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/Highlight.swift b/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift similarity index 95% rename from apple/OmnivoreKit/Sources/Models/DataModels/Highlight.swift rename to apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift index a7d079125..8e2c43c5f 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/Highlight.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift @@ -1,7 +1,7 @@ import CoreData import Foundation -public struct Highlight: Identifiable, Hashable, Codable { +public struct HighlightDep: Identifiable, Hashable, Codable { public let id: String public let shortId: String public let quote: String @@ -54,8 +54,8 @@ public struct Highlight: Identifiable, Hashable, Codable { return persistedHighlight } - public static func make(from persistedHighlight: PersistedHighlight) -> Highlight { - Highlight( + public static func make(from persistedHighlight: PersistedHighlight) -> HighlightDep { + HighlightDep( id: persistedHighlight.id ?? "", shortId: persistedHighlight.shortId ?? "", quote: persistedHighlight.quote ?? "", diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 57287283b..de32ddbd2 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -76,13 +76,13 @@ public extension DataService { } } - func pageFromCache(slug: String) -> ArticleContent? { + func pageFromCache(slug: String) -> ArticleContentDep? { let fetchRequest: NSFetchRequest = PersistedArticleContent.fetchRequest() fetchRequest.predicate = NSPredicate( format: "slug = %@", slug ) if let htmlContent = try? persistentContainer.viewContext.fetch(fetchRequest).first?.htmlContent { - return ArticleContent(htmlContent: htmlContent, highlights: []) + return ArticleContentDep(htmlContent: htmlContent, highlights: []) } else { return nil } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift index 3634157c8..09d9e112e 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift @@ -11,9 +11,9 @@ public extension DataService { patch: String, articleId: String, annotation: String? = nil - ) -> AnyPublisher { + ) -> AnyPublisher { enum MutationResult { - case saved(highlight: Highlight) + case saved(highlight: HighlightDep) case error(errorCode: Enums.CreateHighlightErrorCode) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateLabelPublisher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateLabelPublisher.swift index 6400f87cf..cd266f4f1 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateLabelPublisher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateLabelPublisher.swift @@ -8,9 +8,9 @@ public extension DataService { name: String, color: String, description: String? - ) -> AnyPublisher { + ) -> AnyPublisher { enum MutationResult { - case saved(label: FeedItemLabel) + case saved(label: FeedItemLabelDep) case error(errorCode: Enums.CreateLabelErrorCode) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift index 3575032f3..01d6545c6 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift @@ -12,9 +12,9 @@ public extension DataService { patch: String, articleId: String, overlapHighlightIdList: [String] - ) -> AnyPublisher { + ) -> AnyPublisher { enum MutationResult { - case saved(highlight: Highlight) + case saved(highlight: HighlightDep) case error(errorCode: Enums.MergeHighlightErrorCode) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift index 0809b693a..df552adaa 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleLabelsPublisher.swift @@ -4,9 +4,9 @@ import Models import SwiftGraphQL public extension DataService { - func updateArticleLabelsPublisher(itemID: String, labelIDs: [String]) -> AnyPublisher<[FeedItemLabel], BasicError> { + func updateArticleLabelsPublisher(itemID: String, labelIDs: [String]) -> AnyPublisher<[FeedItemLabelDep], BasicError> { enum MutationResult { - case saved(feedItem: [FeedItemLabel]) + case saved(feedItem: [FeedItemLabelDep]) case error(errorCode: Enums.SetLabelsErrorCode) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift index 33b75a012..8bfde44e0 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift @@ -8,9 +8,9 @@ public extension DataService { highlightID: String, annotation: String?, sharedAt: Date? - ) -> AnyPublisher { + ) -> AnyPublisher { enum MutationResult { - case saved(highlight: Highlight) + case saved(highlight: HighlightDep) case error(errorCode: Enums.UpdateHighlightErrorCode) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index 3218215d8..f63a51c00 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -4,14 +4,14 @@ import Models import SwiftGraphQL public extension DataService { - func articleContentPublisher(username: String, slug: String) -> AnyPublisher { + func articleContentPublisher(username: String, slug: String) -> AnyPublisher { enum QueryResult { - case success(result: ArticleContent) + case success(result: ArticleContentDep) case error(error: String) } let articleSelection = Selection.Article { - ArticleContent( + ArticleContentDep( htmlContent: try $0.content(), highlights: try $0.highlights(selection: highlightSelection.list) ) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LabelsPublisher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LabelsPublisher.swift index 5cce49647..3e01903f9 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LabelsPublisher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LabelsPublisher.swift @@ -4,9 +4,9 @@ import Models import SwiftGraphQL public extension DataService { - func labelsPublisher() -> AnyPublisher<[FeedItemLabel], ServerError> { + func labelsPublisher() -> AnyPublisher<[FeedItemLabelDep], ServerError> { enum QueryResult { - case success(result: [FeedItemLabel]) + case success(result: [FeedItemLabelDep]) case error(error: String) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/PDFHighlightsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/PDFHighlightsQuery.swift index 9dcacd325..7fe2ff470 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/PDFHighlightsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/PDFHighlightsQuery.swift @@ -4,14 +4,14 @@ import Models import SwiftGraphQL public extension DataService { - func pdfHighlightsPublisher(username: String, slug: String) -> AnyPublisher<[Highlight], ServerError> { + func pdfHighlightsPublisher(username: String, slug: String) -> AnyPublisher<[HighlightDep], ServerError> { enum QueryResult { - case success(result: [Highlight]) + case success(result: [HighlightDep]) case error(error: String) } let highlightSelection = Selection.Highlight { - Highlight( + HighlightDep( id: try $0.id(), shortId: try $0.shortId(), quote: try $0.quote(), diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Selections/FeedItemLabelSelection.swift b/apple/OmnivoreKit/Sources/Services/DataService/Selections/FeedItemLabelSelection.swift index 2b0abade0..656392464 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Selections/FeedItemLabelSelection.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Selections/FeedItemLabelSelection.swift @@ -2,7 +2,7 @@ import Models import SwiftGraphQL let feedItemLabelSelection = Selection.Label { - FeedItemLabel( + FeedItemLabelDep( id: try $0.id(), name: try $0.name(), color: try $0.color(), diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Selections/HighlightSelection.swift b/apple/OmnivoreKit/Sources/Services/DataService/Selections/HighlightSelection.swift index 769901b40..276078550 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Selections/HighlightSelection.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Selections/HighlightSelection.swift @@ -2,7 +2,7 @@ import Models import SwiftGraphQL let highlightSelection = Selection.Highlight { - Highlight( + HighlightDep( id: try $0.id(), shortId: try $0.shortId(), quote: try $0.quote(), diff --git a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift index c9a225c11..415be2bb1 100644 --- a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift +++ b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift @@ -4,17 +4,17 @@ import Foundation import Models public extension DataService { - func cachedHighlights(pdfID: String) -> [Highlight] { + func cachedHighlights(pdfID: String) -> [HighlightDep] { let fetchRequest: NSFetchRequest = PersistedHighlight.fetchRequest() fetchRequest.predicate = NSPredicate( format: "associatedItemId = %@ AND markedForDeletion = %@", pdfID, false ) let highlights = (try? persistentContainer.viewContext.fetch(fetchRequest)) ?? [] - return highlights.map { Highlight.make(from: $0) } + return highlights.map { HighlightDep.make(from: $0) } } - func persistHighlight(pdfID: String, highlight: Highlight) { + func persistHighlight(pdfID: String, highlight: HighlightDep) { _ = highlight.toManagedObject( context: persistentContainer.viewContext, associatedItemID: pdfID diff --git a/apple/OmnivoreKit/Sources/Views/TextChip.swift b/apple/OmnivoreKit/Sources/Views/TextChip.swift index c07ce3463..198d42d94 100644 --- a/apple/OmnivoreKit/Sources/Views/TextChip.swift +++ b/apple/OmnivoreKit/Sources/Views/TextChip.swift @@ -8,7 +8,7 @@ public struct TextChip: View { self.color = color } - public init?(feedItemLabel: FeedItemLabel) { + public init?(feedItemLabel: FeedItemLabelDep) { guard let color = Color(hex: feedItemLabel.color) else { return nil } self.text = feedItemLabel.name @@ -41,7 +41,7 @@ public struct TextChipButton: View { } public static func makeRemovableLabelButton( - feedItemLabel: FeedItemLabel, + feedItemLabel: FeedItemLabelDep, onTap: @escaping () -> Void ) -> TextChipButton { TextChipButton( From 0fc7ff0a431f2062fb40055a5c9baad4b85b3c62 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 18 Apr 2022 17:22:47 -0700 Subject: [PATCH 14/76] rename FeedItem to FeedItemDep --- .../Sources/App/PDFSupport/PDFViewerViewModel.swift | 4 ++-- .../Home/Components/FeedCardNavigationLink.swift | 4 ++-- .../Sources/App/Views/Home/HomeFeedViewIOS.swift | 8 ++++---- .../Sources/App/Views/Home/HomeFeedViewMac.swift | 2 +- .../Sources/App/Views/Home/HomeFeedViewModel.swift | 12 ++++++------ .../Sources/App/Views/Labels/ApplyLabelsView.swift | 2 +- .../Sources/App/Views/Labels/LabelsViewModel.swift | 2 +- .../Sources/App/Views/LinkItemDetailView.swift | 6 +++--- .../Sources/App/Views/WebReader/WebReader.swift | 2 +- .../App/Views/WebReader/WebReaderContainer.swift | 2 +- .../App/Views/WebReader/WebReaderContent.swift | 4 ++-- .../Sources/Models/DataModels/FeedItem.swift | 12 ++++++------ .../Sources/Services/DataService/DataService.swift | 2 +- .../Mutations/UpdateArticleReadingProgress.swift | 4 ++-- .../DataService/Queries/LibraryItemsQuery.swift | 8 ++++---- .../Sources/Services/NSNotification+Operation.swift | 2 +- .../Sources/Views/FeedItem/GridCard.swift | 4 ++-- .../Sources/Views/FeedItem/HomeFeedCardView.swift | 4 ++-- apple/OmnivoreKit/Sources/Views/SnoozeView.swift | 4 ++-- apple/Sources/PushNotificationConfig.swift | 2 +- 20 files changed, 45 insertions(+), 45 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift index c1fde6ec3..712d19be3 100644 --- a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift @@ -7,12 +7,12 @@ public final class PDFViewerViewModel: ObservableObject { @Published public var errorMessage: String? @Published public var readerView: Bool = false - public var feedItem: FeedItem + public var feedItem: FeedItemDep var subscriptions = Set() let services: Services - public init(services: Services, feedItem: FeedItem) { + public init(services: Services, feedItem: FeedItemDep) { self.services = services self.feedItem = feedItem } diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift b/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift index dbf0084ca..14e3ccca8 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/Components/FeedCardNavigationLink.swift @@ -6,7 +6,7 @@ import Views struct FeedCardNavigationLink: View { @EnvironmentObject var dataService: DataService - let item: FeedItem + let item: FeedItemDep @ObservedObject var viewModel: HomeFeedViewModel @@ -34,7 +34,7 @@ struct GridCardNavigationLink: View { @State private var scale = 1.0 - let item: FeedItem + let item: FeedItemDep let actionHandler: (GridCardAction) -> Void @Binding var isContextMenuOpen: Bool diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift index 46720c206..343503e49 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewIOS.swift @@ -58,7 +58,7 @@ import Views } } .onReceive(NotificationCenter.default.publisher(for: Notification.Name("PushFeedItem"))) { notification in - if let feedItem = notification.userInfo?["feedItem"] as? FeedItem { + if let feedItem = notification.userInfo?["feedItem"] as? FeedItemDep { viewModel.pushFeedItem(item: feedItem) viewModel.selectedLinkItem = feedItem } @@ -145,7 +145,7 @@ import Views @EnvironmentObject var dataService: DataService @Binding var prefersListLayout: Bool - @State private var itemToRemove: FeedItem? + @State private var itemToRemove: FeedItemDep? @State private var confirmationShown = false @ObservedObject var viewModel: HomeFeedViewModel @@ -269,13 +269,13 @@ import Views struct HomeFeedGridView: View { @EnvironmentObject var dataService: DataService - @State private var itemToRemove: FeedItem? + @State private var itemToRemove: FeedItemDep? @State private var confirmationShown = false @State var isContextMenuOpen = false @ObservedObject var viewModel: HomeFeedViewModel - func contextMenuActionHandler(item: FeedItem, action: GridCardAction) { + func contextMenuActionHandler(item: FeedItemDep, action: GridCardAction) { switch action { case .toggleArchiveStatus: viewModel.setLinkArchived(dataService: dataService, linkId: item.id, archived: !item.isArchived) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift index 3ca413ff5..6f17a64cb 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewMac.swift @@ -9,7 +9,7 @@ import Views #if os(macOS) struct HomeFeedView: View { @EnvironmentObject var dataService: DataService - @State private var itemToRemove: FeedItem? + @State private var itemToRemove: FeedItemDep? @State private var confirmationShown = false @ObservedObject var viewModel: HomeFeedViewModel diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index d2d052fb8..b20389a81 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -14,15 +14,15 @@ import Views /// Track label updates to be committed when user navigates back to grid view var uncommittedLabelUpdates = [String: [FeedItemLabelDep]]() - @Published var items = [FeedItem]() + @Published var items = [FeedItemDep]() @Published var isLoading = false @Published var showPushNotificationPrimer = false - @Published var itemUnderLabelEdit: FeedItem? + @Published var itemUnderLabelEdit: FeedItemDep? @Published var searchTerm = "" @Published var selectedLabels = [FeedItemLabelDep]() @Published var snoozePresented = false - @Published var itemToSnooze: FeedItem? - @Published var selectedLinkItem: FeedItem? + @Published var itemToSnooze: FeedItemDep? + @Published var selectedLinkItem: FeedItemDep? var cursor: String? var sendProgressUpdates = false @@ -36,7 +36,7 @@ import Views init() {} - func itemAppeared(item: FeedItem, dataService: DataService) { + func itemAppeared(item: FeedItemDep, dataService: DataService) { if isLoading { return } let itemIndex = items.firstIndex(where: { $0.id == item.id }) let thresholdIndex = items.index(items.endIndex, offsetBy: -5) @@ -47,7 +47,7 @@ import Views } } - func pushFeedItem(item: FeedItem) { + func pushFeedItem(item: FeedItemDep) { items.insert(item, at: 0) } diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift index 8e2fa47f1..99ffb4446 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/ApplyLabelsView.swift @@ -5,7 +5,7 @@ import Views struct ApplyLabelsView: View { enum Mode { - case item(FeedItem) + case item(FeedItemDep) case list([FeedItemLabelDep]) var navTitle: String { diff --git a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift index d35f3c9d7..729f72f13 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Labels/LabelsViewModel.swift @@ -19,7 +19,7 @@ final class LabelsViewModel: ObservableObject { /// - dataService: `DataService` reference /// - item: Optional `FeedItem` for applying labels to a single item /// - initiallySelectedLabels: Optional `[FeedItemLabel]` for filtering a list of items - func loadLabels(dataService: DataService, item: FeedItem? = nil, initiallySelectedLabels: [FeedItemLabelDep]? = nil) { + func loadLabels(dataService: DataService, item: FeedItemDep? = nil, initiallySelectedLabels: [FeedItemLabelDep]? = nil) { guard !hasLoadedInitialLabels else { return } isLoading = true diff --git a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift index a3de5f6a8..dc30ef228 100644 --- a/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift +++ b/apple/OmnivoreKit/Sources/App/Views/LinkItemDetailView.swift @@ -6,17 +6,17 @@ import Utils import Views enum PDFProvider { - static var pdfViewerProvider: ((URL, FeedItem) -> AnyView)? + static var pdfViewerProvider: ((URL, FeedItemDep) -> AnyView)? } @MainActor final class LinkItemDetailViewModel: ObservableObject { let homeFeedViewModel: HomeFeedViewModel - @Published var item: FeedItem + @Published var item: FeedItemDep @Published var webAppWrapperViewModel: WebAppWrapperViewModel? var subscriptions = Set() - init(item: FeedItem, homeFeedViewModel: HomeFeedViewModel) { + init(item: FeedItemDep, homeFeedViewModel: HomeFeedViewModel) { self.item = item self.homeFeedViewModel = homeFeedViewModel } diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index c7c678c2e..e38dd8c95 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -7,7 +7,7 @@ import WebKit #if os(iOS) struct WebReader: UIViewRepresentable { let articleContent: ArticleContentDep - let item: FeedItem + let item: FeedItemDep let openLinkAction: (URL) -> Void let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void let navBarVisibilityRatioUpdater: (Double) -> Void diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index d0c8cf42d..dbfadd324 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -7,7 +7,7 @@ import WebKit #if os(iOS) struct WebReaderContainerView: View { - let item: FeedItem + let item: FeedItemDep let homeFeedViewModel: HomeFeedViewModel @State private var showFontSizePopover = false diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift index 8414c374d..42a2f1a26 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift @@ -5,12 +5,12 @@ import Utils struct WebReaderContent { let textFontSize: Int let articleContent: ArticleContentDep - let item: FeedItem + let item: FeedItemDep let themeKey: String init( articleContent: ArticleContentDep, - item: FeedItem, + item: FeedItemDep, isDark: Bool, fontSize: Int ) { diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift index f7618d6bc..9970f1c63 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift @@ -1,16 +1,16 @@ import Foundation public struct HomeFeedData { - public let items: [FeedItem] + public let items: [FeedItemDep] public let cursor: String? - public init(items: [FeedItem], cursor: String?) { + public init(items: [FeedItemDep], cursor: String?) { self.items = items self.cursor = cursor } } -public struct FeedItem: Identifiable, Hashable { +public struct FeedItemDep: Identifiable, Hashable { public let id: String public let title: String public let createdAt: Date @@ -70,7 +70,7 @@ public struct FeedItem: Identifiable, Hashable { self.labels = labels } - public static func fromJsonArticle(linkData: Data) -> FeedItem? { + public static func fromJsonArticle(linkData: Data) -> FeedItemDep? { try? JSONDecoder().decode(JSONArticle.self, from: linkData).feedItem } @@ -114,8 +114,8 @@ struct JSONArticle: Decodable { let url: String let isArchived: Bool - var feedItem: FeedItem { - FeedItem( + var feedItem: FeedItemDep { + FeedItemDep( id: id, title: title, createdAt: createdAt, diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index de32ddbd2..08e39f1aa 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -63,7 +63,7 @@ public final class DataService: ObservableObject { } public extension DataService { - func prefetchPages(items: [FeedItem]) { + func prefetchPages(items: [FeedItemDep]) { guard let username = currentViewer?.username else { return } for item in items { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleReadingProgress.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleReadingProgress.swift index 93ccf5c64..130541350 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleReadingProgress.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateArticleReadingProgress.swift @@ -8,9 +8,9 @@ public extension DataService { itemID: String, readingProgress: Double, anchorIndex: Int - ) -> AnyPublisher { + ) -> AnyPublisher { enum MutationResult { - case saved(feedItem: FeedItem) + case saved(feedItem: FeedItemDep) case error(errorCode: Enums.SaveArticleReadingProgressErrorCode) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift index d6f31e93c..e43d63efb 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift @@ -4,7 +4,7 @@ import Models import SwiftGraphQL public extension DataService { - func articlePublisher(slug: String) -> AnyPublisher { + func articlePublisher(slug: String) -> AnyPublisher { internalViewerPublisher() .flatMap { self.internalArticlePublisher(username: $0.username ?? "", slug: slug) } .receive(on: DispatchQueue.main) @@ -13,9 +13,9 @@ public extension DataService { } extension DataService { - func internalArticlePublisher(username: String, slug: String) -> AnyPublisher { + func internalArticlePublisher(username: String, slug: String) -> AnyPublisher { enum QueryResult { - case success(result: FeedItem) + case success(result: FeedItemDep) case error(error: String) } @@ -126,7 +126,7 @@ public extension DataService { } let homeFeedItemSelection = Selection.Article { - FeedItem( + FeedItemDep( id: try $0.id(), title: try $0.title(), createdAt: try $0.createdAt().value ?? Date(), diff --git a/apple/OmnivoreKit/Sources/Services/NSNotification+Operation.swift b/apple/OmnivoreKit/Sources/Services/NSNotification+Operation.swift index c7fdd5682..0aad9b201 100644 --- a/apple/OmnivoreKit/Sources/Services/NSNotification+Operation.swift +++ b/apple/OmnivoreKit/Sources/Services/NSNotification+Operation.swift @@ -32,7 +32,7 @@ public extension NSNotification { return nil } - static func pushFeedItem(feedItem: FeedItem) { + static func pushFeedItem(feedItem: FeedItemDep) { NotificationCenter.default.post(name: NSNotification.PushFeedItem, object: nil, userInfo: ["feedItem": feedItem]) } diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift index 3d75ed4b0..a10a29459 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/GridCard.swift @@ -10,12 +10,12 @@ public enum GridCardAction { public struct GridCard: View { @Binding var isContextMenuOpen: Bool - let item: FeedItem + let item: FeedItemDep let actionHandler: (GridCardAction) -> Void let tapAction: () -> Void public init( - item: FeedItem, + item: FeedItemDep, isContextMenuOpen: Binding, actionHandler: @escaping (GridCardAction) -> Void, tapAction: @escaping () -> Void diff --git a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift index 5ae355eea..3c437557b 100644 --- a/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift +++ b/apple/OmnivoreKit/Sources/Views/FeedItem/HomeFeedCardView.swift @@ -3,9 +3,9 @@ import SwiftUI import Utils public struct FeedCard: View { - let item: FeedItem + let item: FeedItemDep - public init(item: FeedItem) { + public init(item: FeedItemDep) { self.item = item } diff --git a/apple/OmnivoreKit/Sources/Views/SnoozeView.swift b/apple/OmnivoreKit/Sources/Views/SnoozeView.swift index 8c23a926e..4b495efe8 100644 --- a/apple/OmnivoreKit/Sources/Views/SnoozeView.swift +++ b/apple/OmnivoreKit/Sources/Views/SnoozeView.swift @@ -3,12 +3,12 @@ import SwiftUI public struct SnoozeView: View { @Binding var snoozePresented: Bool - @Binding var itemToSnooze: FeedItem? + @Binding var itemToSnooze: FeedItemDep? let snoozeAction: (SnoozeActionParams) -> Void public init( snoozePresented: Binding, - itemToSnooze: Binding, + itemToSnooze: Binding, snoozeAction: @escaping (SnoozeActionParams) -> Void ) { self._snoozePresented = snoozePresented diff --git a/apple/Sources/PushNotificationConfig.swift b/apple/Sources/PushNotificationConfig.swift index 7927cbd6f..96e68e557 100644 --- a/apple/Sources/PushNotificationConfig.swift +++ b/apple/Sources/PushNotificationConfig.swift @@ -57,7 +57,7 @@ extension AppDelegate: UNUserNotificationCenterDelegate { let userInfo = response.notification.request.content.userInfo if let linkData = userInfo["link"] as? String { guard let jsonData = Data(base64Encoded: linkData) else { return } - if let item = FeedItem.fromJsonArticle(linkData: jsonData) { + if let item = FeedItemDep.fromJsonArticle(linkData: jsonData) { NSNotification.pushFeedItem(feedItem: item) } } From 70c9080766baeba6ec88529126fda28867f0d344 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 18 Apr 2022 20:14:16 -0700 Subject: [PATCH 15/76] rename cordata entities --- .../CoreDataModel.xcdatamodel/contents | 94 +++++++++---------- .../Models/DataModels/HighlightDep.swift | 52 +++++----- .../Services/DataService/DataService.swift | 2 +- .../CachedPDFHighlights.swift | 12 +-- 4 files changed, 80 insertions(+), 80 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index 568d9d5e3..c15b134f4 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -1,5 +1,48 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -31,49 +74,6 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - @@ -86,11 +86,11 @@ - - - - + + + + \ No newline at end of file diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift b/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift index 8e2c43c5f..affd5863b 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift @@ -37,35 +37,35 @@ public struct HighlightDep: Identifiable, Hashable, Codable { self.createdByMe = createdByMe } - public func toManagedObject(context: NSManagedObjectContext, associatedItemID: String) -> PersistedHighlight { - let persistedHighlight = PersistedHighlight(context: context) - persistedHighlight.associatedItemId = associatedItemID - persistedHighlight.markedForDeletion = false - persistedHighlight.id = id - persistedHighlight.shortId = shortId - persistedHighlight.quote = quote - persistedHighlight.prefix = prefix - persistedHighlight.suffix = suffix - persistedHighlight.patch = patch - persistedHighlight.annotation = annotation - persistedHighlight.createdAt = createdAt - persistedHighlight.updatedAt = updatedAt - persistedHighlight.createdByMe = createdByMe - return persistedHighlight + public func toManagedObject(context: NSManagedObjectContext, associatedItemID: String) -> Highlight { + let highlight = Highlight(context: context) + highlight.associatedItemId = associatedItemID + highlight.markedForDeletion = false + highlight.id = id + highlight.shortId = shortId + highlight.quote = quote + highlight.prefix = prefix + highlight.suffix = suffix + highlight.patch = patch + highlight.annotation = annotation + highlight.createdAt = createdAt + highlight.updatedAt = updatedAt + highlight.createdByMe = createdByMe + return highlight } - public static func make(from persistedHighlight: PersistedHighlight) -> HighlightDep { + public static func make(from highlight: Highlight) -> HighlightDep { HighlightDep( - id: persistedHighlight.id ?? "", - shortId: persistedHighlight.shortId ?? "", - quote: persistedHighlight.quote ?? "", - prefix: persistedHighlight.prefix, - suffix: persistedHighlight.suffix, - patch: persistedHighlight.patch ?? "", - annotation: persistedHighlight.annotation, - createdByMe: persistedHighlight.createdByMe, - createdAt: persistedHighlight.createdAt, - updatedAt: persistedHighlight.updatedAt + id: highlight.id ?? "", + shortId: highlight.shortId ?? "", + quote: highlight.quote ?? "", + prefix: highlight.prefix, + suffix: highlight.suffix, + patch: highlight.patch ?? "", + annotation: highlight.annotation, + createdByMe: highlight.createdByMe, + createdAt: highlight.createdAt, + updatedAt: highlight.updatedAt ) } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index 08e39f1aa..e88b97434 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -37,7 +37,7 @@ public final class DataService: ObservableObject { public func clearHighlights() { deletedHighlightsIDs.removeAll() - let fetchRequest: NSFetchRequest = PersistedHighlight.fetchRequest() + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() let highlights = (try? persistentContainer.viewContext.fetch(fetchRequest)) ?? [] diff --git a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift index 415be2bb1..f4b7ae9da 100644 --- a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift +++ b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift @@ -5,7 +5,7 @@ import Models public extension DataService { func cachedHighlights(pdfID: String) -> [HighlightDep] { - let fetchRequest: NSFetchRequest = PersistedHighlight.fetchRequest() + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() fetchRequest.predicate = NSPredicate( format: "associatedItemId = %@ AND markedForDeletion = %@", pdfID, false ) @@ -22,10 +22,10 @@ public extension DataService { do { try persistentContainer.viewContext.save() - print("PersistedHighlight saved succesfully") + print("Highlight saved succesfully") } catch { persistentContainer.viewContext.rollback() - print("Failed to save PersistedHighlight: \(error)") + print("Failed to save Highlight: \(error)") } } @@ -34,7 +34,7 @@ public extension DataService { deletedHighlightsIDs.insert(highlightID) } - let fetchRequest: NSFetchRequest = PersistedHighlight.fetchRequest() + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() fetchRequest.predicate = NSPredicate(format: "id IN %@", highlightIds) guard let highlights = try? persistentContainer.viewContext.fetch(fetchRequest) else { return } @@ -44,10 +44,10 @@ public extension DataService { do { try persistentContainer.viewContext.save() - print("PersistedHighlight(s) updated succesfully") + print("Highlight(s) updated succesfully") } catch { persistentContainer.viewContext.rollback() - print("Failed to update PersistedHighlight(s): \(error)") + print("Failed to update Highlight(s): \(error)") } } } From a7a048c3aa3c6f45a65f98c2eec4b9daaf318776 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 18 Apr 2022 21:20:28 -0700 Subject: [PATCH 16/76] update highlight coredata model --- .../CoreDataModel.xcdatamodel/contents | 8 +++++--- .../Sources/Models/DataModels/HighlightDep.swift | 2 +- .../PersistableModels/CachedPDFHighlights.swift | 2 +- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index c15b134f4..b3cb44951 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -2,10 +2,10 @@ - + @@ -24,6 +24,7 @@ + @@ -36,6 +37,7 @@ + @@ -86,11 +88,11 @@ + + - - \ No newline at end of file diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift b/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift index affd5863b..67e73bfda 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift @@ -39,7 +39,7 @@ public struct HighlightDep: Identifiable, Hashable, Codable { public func toManagedObject(context: NSManagedObjectContext, associatedItemID: String) -> Highlight { let highlight = Highlight(context: context) - highlight.associatedItemId = associatedItemID + highlight.linkedItemId = associatedItemID highlight.markedForDeletion = false highlight.id = id highlight.shortId = shortId diff --git a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift index f4b7ae9da..b036a6b8c 100644 --- a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift +++ b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift @@ -7,7 +7,7 @@ public extension DataService { func cachedHighlights(pdfID: String) -> [HighlightDep] { let fetchRequest: NSFetchRequest = Highlight.fetchRequest() fetchRequest.predicate = NSPredicate( - format: "associatedItemId = %@ AND markedForDeletion = %@", pdfID, false + format: "linkedItemId = %@ AND markedForDeletion = %@", pdfID, false ) let highlights = (try? persistentContainer.viewContext.fetch(fetchRequest)) ?? [] From 1b4c87127621188950363605e18c4db6650e2d33 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Mon, 18 Apr 2022 22:11:50 -0700 Subject: [PATCH 17/76] use coredata highlight model in some places --- .../App/Views/WebReader/WebReaderViewModel.swift | 10 +++++----- .../Sources/Models/DataModels/HighlightDep.swift | 14 ++++++++++++++ .../DataService/Mutations/CreateHighlight.swift | 11 +++++++++-- .../DataService/Mutations/MergeHighlight.swift | 11 +++++++++-- 4 files changed, 37 insertions(+), 9 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 3895f6b35..3d92e3a0a 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -61,8 +61,8 @@ final class WebReaderViewModel: ObservableObject { guard case .failure = completion else { return } replyHandler([], "createHighlight: Error encoding response") } receiveValue: { highlight in - if let highlight = encodeHighlightResult(highlight) { - replyHandler(["result": highlight], nil) + if let highlightValue = encodeHighlightResult(HighlightDep.make(from: highlight)) { + replyHandler(["result": highlightValue], nil) } else { replyHandler([], "createHighlight: Error encoding response") } @@ -104,10 +104,10 @@ final class WebReaderViewModel: ObservableObject { guard case .failure = completion else { return } replyHandler([], "mergeHighlight: Error encoding response") } receiveValue: { highlight in - if let highlight = encodeHighlightResult(highlight) { - replyHandler(["result": highlight], nil) + if let highlightValue = encodeHighlightResult(HighlightDep.make(from: highlight)) { + replyHandler(["result": highlightValue], nil) } else { - replyHandler([], "mergeHighlight: Error encoding response") + replyHandler([], "createHighlight: Error encoding response") } } .store(in: &subscriptions) diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift b/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift index 67e73bfda..25a659703 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/HighlightDep.swift @@ -54,6 +54,20 @@ public struct HighlightDep: Identifiable, Hashable, Codable { return highlight } + public func persist(context: NSManagedObjectContext, associatedItemID: String) -> Highlight? { + let highlight = toManagedObject(context: context, associatedItemID: associatedItemID) + + do { + try context.save() + print("Highlight saved succesfully") + return highlight + } catch { + context.rollback() + print("Failed to save Highlight: \(error.localizedDescription)") + return nil + } + } + public static func make(from highlight: Highlight) -> HighlightDep { HighlightDep( id: highlight.id ?? "", diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift index 09d9e112e..dc669f0df 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift @@ -11,7 +11,7 @@ public extension DataService { patch: String, articleId: String, annotation: String? = nil - ) -> AnyPublisher { + ) -> AnyPublisher { enum MutationResult { case saved(highlight: HighlightDep) case error(errorCode: Enums.CreateHighlightErrorCode) @@ -54,7 +54,14 @@ public extension DataService { switch payload.data { case let .saved(highlight: highlight): - promise(.success(highlight)) + if let highlightObject = highlight.persist( + context: self.persistentContainer.viewContext, + associatedItemID: articleId + ) { + promise(.success(highlightObject)) + } else { + promise(.failure(.message(messageText: "core data error"))) + } case let .error(errorCode: errorCode): promise(.failure(.message(messageText: errorCode.rawValue))) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift index 01d6545c6..7a762c83d 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift @@ -12,7 +12,7 @@ public extension DataService { patch: String, articleId: String, overlapHighlightIdList: [String] - ) -> AnyPublisher { + ) -> AnyPublisher { enum MutationResult { case saved(highlight: HighlightDep) case error(errorCode: Enums.MergeHighlightErrorCode) @@ -58,7 +58,14 @@ public extension DataService { switch payload.data { case let .saved(highlight: highlight): - promise(.success(highlight)) + if let highlightObject = highlight.persist( + context: self.persistentContainer.viewContext, + associatedItemID: articleId + ) { + promise(.success(highlightObject)) + } else { + promise(.failure(.message(messageText: "core data error"))) + } case let .error(errorCode: errorCode): promise(.failure(.message(messageText: errorCode.rawValue))) } From bb016ce7f442ca01f04b2d1ee1e2f6d3a21223b9 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 19 Apr 2022 11:18:40 -0700 Subject: [PATCH 18/76] save highlightsJSON on persisted article content --- .../CoreDataModel.xcdatamodel/contents | 3 ++- .../Sources/Models/DataModels/ArticleContentDep.swift | 9 ++++++++- .../Sources/Services/DataService/DataService.swift | 8 ++++++-- .../DataService/Queries/ArticleContentQuery.swift | 11 ++++++++--- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents index b3cb44951..6cd4746fd 100644 --- a/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents +++ b/apple/OmnivoreKit/Sources/Models/CoreData/CoreDataModel.xcdatamodeld/CoreDataModel.xcdatamodel/contents @@ -68,6 +68,7 @@ + @@ -92,7 +93,7 @@ - + \ No newline at end of file diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContentDep.swift b/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContentDep.swift index 101900231..f33fec12f 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContentDep.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/ArticleContentDep.swift @@ -3,16 +3,23 @@ import Foundation public struct ArticleContentDep { public let htmlContent: String public let highlights: [HighlightDep] + public let storedHighlightsJSONString: String? public init( htmlContent: String, - highlights: [HighlightDep] + highlights: [HighlightDep], + storedHighlightsJSONString: String? ) { self.htmlContent = htmlContent self.highlights = highlights + self.storedHighlightsJSONString = storedHighlightsJSONString } public var highlightsJSONString: String { + if let storedHighlightsJSONString = storedHighlightsJSONString { + return storedHighlightsJSONString + } + let jsonData = try? JSONEncoder().encode(highlights) guard let jsonData = jsonData else { return "[]" } return String(data: jsonData, encoding: .utf8) ?? "[]" diff --git a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift index e88b97434..5ff65440e 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/DataService.swift @@ -81,8 +81,12 @@ public extension DataService { fetchRequest.predicate = NSPredicate( format: "slug = %@", slug ) - if let htmlContent = try? persistentContainer.viewContext.fetch(fetchRequest).first?.htmlContent { - return ArticleContentDep(htmlContent: htmlContent, highlights: []) + if let articleContent = try? persistentContainer.viewContext.fetch(fetchRequest).first { + return ArticleContentDep( + htmlContent: articleContent.htmlContent ?? "", + highlights: [], + storedHighlightsJSONString: articleContent.highlightsJSONString + ) } else { return nil } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index f63a51c00..703f8683b 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -13,7 +13,8 @@ public extension DataService { let articleSelection = Selection.Article { ArticleContentDep( htmlContent: try $0.content(), - highlights: try $0.highlights(selection: highlightSelection.list) + highlights: try $0.highlights(selection: highlightSelection.list), + storedHighlightsJSONString: nil ) } @@ -43,7 +44,7 @@ public extension DataService { switch payload.data { case let .success(result: result): // store result in core data - self?.persistArticleContent(htmlContent: result.htmlContent, slug: slug) + self?.persistArticleContent(htmlContent: result.htmlContent, slug: slug, highlights: result.highlights) promise(.success(result)) case .error: promise(.failure(.unknown)) @@ -60,11 +61,15 @@ public extension DataService { } extension DataService { - func persistArticleContent(htmlContent: String, slug: String) { + func persistArticleContent(htmlContent: String, slug: String, highlights: [HighlightDep]) { let persistedArticleContent = PersistedArticleContent(context: persistentContainer.viewContext) persistedArticleContent.htmlContent = htmlContent persistedArticleContent.slug = slug + if let jsonData = try? JSONEncoder().encode(highlights) { + persistedArticleContent.highlightsJSONString = String(data: jsonData, encoding: .utf8) + } + do { try persistentContainer.viewContext.save() print("PersistedArticleContent saved succesfully") From ed69e36576999ea2189a31d04d3e51496fdf2871 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 19 Apr 2022 11:25:40 -0700 Subject: [PATCH 19/76] remove unused publishers --- .../Queries/LibraryItemsQuery.swift | 51 ------------------- .../DataService/Queries/ViewerFetcher.swift | 43 ---------------- 2 files changed, 94 deletions(-) diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift index e43d63efb..766a34ecb 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift @@ -3,57 +3,6 @@ import Foundation import Models import SwiftGraphQL -public extension DataService { - func articlePublisher(slug: String) -> AnyPublisher { - internalViewerPublisher() - .flatMap { self.internalArticlePublisher(username: $0.username ?? "", slug: slug) } - .receive(on: DispatchQueue.main) - .eraseToAnyPublisher() - } -} - -extension DataService { - func internalArticlePublisher(username: String, slug: String) -> AnyPublisher { - enum QueryResult { - case success(result: FeedItemDep) - case error(error: String) - } - - let selection = Selection { - try $0.on( - articleSuccess: .init { QueryResult.success(result: try $0.article(selection: homeFeedItemSelection)) }, - articleError: .init { QueryResult.error(error: try $0.errorCodes().description) } - ) - } - - let query = Selection.Query { - try $0.article(username: username, slug: slug, selection: selection) - } - - let path = appEnvironment.graphqlPath - let headers = networker.defaultHeaders - - return Deferred { - Future { promise in - send(query, to: path, headers: headers) { result in - switch result { - case let .success(payload): - switch payload.data { - case let .success(result: result): - promise(.success(result)) - case let .error(error: error): - promise(.failure(.message(messageText: error.debugDescription))) - } - case .failure: - promise(.failure(.message(messageText: "ger article fetch failed"))) - } - } - } - } - .eraseToAnyPublisher() - } -} - public extension DataService { func libraryItemsPublisher( limit: Int, diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift index f526c23f3..2e2e46367 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ViewerFetcher.swift @@ -49,49 +49,6 @@ public extension DataService { } } -extension DataService { - @available(*, deprecated, message: "use async version instead") - func internalViewerPublisher() -> AnyPublisher { - let selection = Selection { - ViewerInternal( - userID: try $0.id(), - username: try $0.profile( - selection: .init { try $0.username() } - ), - name: try $0.name(), - profileImageURL: try $0.profile( - selection: .init { try $0.pictureUrl() } - ) - ) - } - - let query = Selection.Query { - try $0.me(selection: selection.nonNullOrFail) - } - - let path = appEnvironment.graphqlPath - let headers = networker.defaultHeaders - - return Deferred { - Future { [weak self] promise in - send(query, to: path, headers: headers) { result in - switch result { - case let .success(payload): - if let self = self, let viewer = payload.data.persist(context: self.persistentContainer.viewContext) { - promise(.success(viewer)) - } else { - promise(.failure(.message(messageText: "coredata error"))) - } - case .failure: - promise(.failure(.message(messageText: "http error"))) - } - } - } - } - .eraseToAnyPublisher() - } -} - private struct ViewerInternal { let userID: String let username: String From 1012fccbb232020cc648741980cee3548807b946 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 19 Apr 2022 11:50:17 -0700 Subject: [PATCH 20/76] save linkeditems to core data --- .../Sources/Models/DataModels/FeedItem.swift | 46 +++++++++++++++++++ .../Queries/LibraryItemsQuery.swift | 2 + 2 files changed, 48 insertions(+) diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift index 9970f1c63..9c162178f 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift @@ -1,3 +1,4 @@ +import CoreData import Foundation public struct HomeFeedData { @@ -137,3 +138,48 @@ struct JSONArticle: Decodable { ) } } + +public extension FeedItemDep { + func asManagedObject(inContext context: NSManagedObjectContext) -> LinkedItem { + let linkedItem = LinkedItem(context: context) + + linkedItem.id = id + linkedItem.title = title + linkedItem.createdAt = createdAt + linkedItem.savedAt = savedAt + linkedItem.readingProgress = readingProgress + linkedItem.readingProgressAnchor = Int64(readingProgressAnchor) + linkedItem.imageURLString = imageURLString + linkedItem.onDeviceImageURLString = onDeviceImageURLString + linkedItem.pageURLString = pageURLString + linkedItem.descriptionText = descriptionText + linkedItem.publisherURLString = publisherURLString + linkedItem.author = author + linkedItem.publishDate = publishDate + linkedItem.slug = slug + linkedItem.isArchived = isArchived + linkedItem.contentReader = contentReader + + // for label in item.labels { + // TODO: append labels + // } + + return linkedItem + } +} + +public extension Sequence where Element == FeedItemDep { + func persist(context: NSManagedObjectContext) -> [LinkedItem]? { + let linkedItems = map { $0.asManagedObject(inContext: context) } + + do { + try context.save() + print("LinkedItems saved succesfully") + return linkedItems + } catch { + context.rollback() + print("Failed to save LinkedItems: \(error.localizedDescription)") + return nil + } + } +} diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift index 766a34ecb..5ec018a76 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift @@ -59,6 +59,8 @@ public extension DataService { case let .success(payload): switch payload.data { case let .success(result: result): + // save items to coredata + _ = result.items.persist(context: self.persistentContainer.viewContext) promise(.success(result)) case .error: promise(.failure(.unknown)) From 93ef6980d1103a2d4efbb7839d266fb555621ced Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 19 Apr 2022 12:08:19 -0700 Subject: [PATCH 21/76] fetch items from core data if network request fails --- .../App/Views/Home/HomeFeedViewModel.swift | 3 +++ .../Sources/Models/DataModels/FeedItem.swift | 23 +++++++++++++++++++ .../Queries/LibraryItemsQuery.swift | 8 +++++++ 3 files changed, 34 insertions(+) diff --git a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift index b20389a81..8da3422cb 100644 --- a/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/Home/HomeFeedViewModel.swift @@ -76,6 +76,9 @@ import Views receiveCompletion: { [weak self] completion in guard case .failure = completion else { return } self?.isLoading = false + // return cachedItems found in CoreData when request fails + self?.items = dataService.cachedFeedItems() + self?.cursor = nil }, receiveValue: { [weak self] result in // Search results aren't guaranteed to return in order so this diff --git a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift index 9c162178f..c84bf93ca 100644 --- a/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift +++ b/apple/OmnivoreKit/Sources/Models/DataModels/FeedItem.swift @@ -166,6 +166,29 @@ public extension FeedItemDep { return linkedItem } + + static func make(from item: LinkedItem) -> FeedItemDep { + FeedItemDep( + id: item.id ?? "", + title: item.title ?? "", + createdAt: item.createdAt ?? Date(), + savedAt: item.savedAt ?? Date(), + readingProgress: item.readingProgress, + readingProgressAnchor: Int(item.readingProgressAnchor), + imageURLString: item.imageURLString, + onDeviceImageURLString: item.onDeviceImageURLString, + documentDirectoryPath: nil, + pageURLString: item.pageURLString ?? "", + descriptionText: item.title, + publisherURLString: item.publisherURLString, + author: item.author, + publishDate: item.publishDate, + slug: item.slug ?? "", + isArchived: item.isArchived, + contentReader: item.contentReader, + labels: [] + ) + } } public extension Sequence where Element == FeedItemDep { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift index 5ec018a76..f00dc9466 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/LibraryItemsQuery.swift @@ -1,4 +1,5 @@ import Combine +import CoreData import Foundation import Models import SwiftGraphQL @@ -74,6 +75,13 @@ public extension DataService { .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } + + func cachedFeedItems() -> [FeedItemDep] { + let fetchRequest: NSFetchRequest = LinkedItem.fetchRequest() + // TODO: set sort order? + let items = (try? persistentContainer.viewContext.fetch(fetchRequest)) ?? [] + return items.map { FeedItemDep.make(from: $0) } + } } let homeFeedItemSelection = Selection.Article { From 6d2cfbe4a6a1d4698bb5ff9f6a63b6ebb7bc66b2 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 19 Apr 2022 12:09:45 -0700 Subject: [PATCH 22/76] bump apple app versions to 1.5.0 --- apple/Omnivore.xcodeproj/project.pbxproj | 32 ++++++++++++------------ 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/apple/Omnivore.xcodeproj/project.pbxproj b/apple/Omnivore.xcodeproj/project.pbxproj index 0a4d64fc6..c0407f6a6 100644 --- a/apple/Omnivore.xcodeproj/project.pbxproj +++ b/apple/Omnivore.xcodeproj/project.pbxproj @@ -1251,7 +1251,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = InfoPlists/ShareExtensionMac.plist; @@ -1261,7 +1261,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.4.0; + MARKETING_VERSION = 1.5.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac"; @@ -1282,7 +1282,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; INFOPLIST_FILE = InfoPlists/ShareExtensionMac.plist; @@ -1292,7 +1292,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.4.0; + MARKETING_VERSION = 1.5.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.ShareExtension-Mac"; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1363,7 +1363,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; @@ -1374,7 +1374,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.4.0; + MARKETING_VERSION = 1.5.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; @@ -1397,7 +1397,7 @@ CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; - CURRENT_PROJECT_VERSION = 49; + CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = ""; DEVELOPMENT_TEAM = QJF2XZ86HB; ENABLE_HARDENED_RUNTIME = YES; @@ -1408,7 +1408,7 @@ "@executable_path/../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.4.0; + MARKETING_VERSION = 1.5.0; MTL_FAST_MATH = YES; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = "$(TARGET_NAME)"; @@ -1463,7 +1463,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.5.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1495,7 +1495,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.5.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -1534,7 +1534,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.5.0; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "-framework", @@ -1573,7 +1573,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.4.0; + MARKETING_VERSION = 1.5.0; MTL_ENABLE_DEBUG_INFO = INCLUDE_SOURCE; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( @@ -1611,7 +1611,7 @@ "@executable_path/../../../../Frameworks", ); MACOSX_DEPLOYMENT_TARGET = 12.0; - MARKETING_VERSION = 1.4.0; + MARKETING_VERSION = 1.5.0; MTL_FAST_MATH = YES; OTHER_LDFLAGS = ( "-framework", @@ -1696,7 +1696,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.5.0; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension"; PRODUCT_NAME = ShareExtension; SDKROOT = iphoneos; @@ -1750,7 +1750,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.5.0; PRODUCT_BUNDLE_IDENTIFIER = app.omnivore.app; PRODUCT_NAME = Omnivore; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1778,7 +1778,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 1.4.2; + MARKETING_VERSION = 1.5.0; PRODUCT_BUNDLE_IDENTIFIER = "app.omnivore.app.share-extension"; PRODUCT_NAME = ShareExtension; SDKROOT = iphoneos; From 33342deb5d7aca075f0a722b35aef436991cfa33 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 19 Apr 2022 13:52:06 -0700 Subject: [PATCH 23/76] create an internal highlight function for encoding --- .../Views/WebReader/WebReaderViewModel.swift | 19 +++---- .../Mutations/CreateHighlight.swift | 13 ++--- .../Mutations/MergeHighlight.swift | 13 ++--- .../Mutations/UpdateHighlightAttributes.swift | 10 ++-- .../Queries/ArticleContentQuery.swift | 2 +- .../Queries/PDFHighlightsQuery.swift | 15 +----- .../Selections/HighlightSelection.swift | 15 ++++++ .../InternalModels/InternalHighlight.swift | 52 +++++++++++++++++++ 8 files changed, 93 insertions(+), 46 deletions(-) create mode 100644 apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift index 3d92e3a0a..55d3d4cec 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderViewModel.swift @@ -9,11 +9,6 @@ struct SafariWebLink: Identifiable { let url: URL } -func encodeHighlightResult(_ highlight: HighlightDep) -> [String: Any]? { - guard let data = try? JSONEncoder().encode(highlight) else { return nil } - return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] -} - final class WebReaderViewModel: ObservableObject { @Published var isLoading = false @Published var articleContent: ArticleContentDep? @@ -60,9 +55,9 @@ final class WebReaderViewModel: ObservableObject { .sink { completion in guard case .failure = completion else { return } replyHandler([], "createHighlight: Error encoding response") - } receiveValue: { highlight in - if let highlightValue = encodeHighlightResult(HighlightDep.make(from: highlight)) { - replyHandler(["result": highlightValue], nil) + } receiveValue: { result in + if let result = result { + replyHandler(["result": result], nil) } else { replyHandler([], "createHighlight: Error encoding response") } @@ -103,8 +98,8 @@ final class WebReaderViewModel: ObservableObject { .sink { completion in guard case .failure = completion else { return } replyHandler([], "mergeHighlight: Error encoding response") - } receiveValue: { highlight in - if let highlightValue = encodeHighlightResult(HighlightDep.make(from: highlight)) { + } receiveValue: { result in + if let highlightValue = result { replyHandler(["result": highlightValue], nil) } else { replyHandler([], "createHighlight: Error encoding response") @@ -126,9 +121,9 @@ final class WebReaderViewModel: ObservableObject { .sink { completion in guard case .failure = completion else { return } replyHandler([], "updateHighlight: Error encoding response") - } receiveValue: { highlight in + } receiveValue: { highlightID in // Update highlight JS code just expects the highlight ID back - replyHandler(["result": highlight.id], nil) + replyHandler(["result": highlightID], nil) } .store(in: &subscriptions) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift index dc669f0df..bcad48562 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/CreateHighlight.swift @@ -11,9 +11,9 @@ public extension DataService { patch: String, articleId: String, annotation: String? = nil - ) -> AnyPublisher { + ) -> AnyPublisher<[String: Any]?, BasicError> { enum MutationResult { - case saved(highlight: HighlightDep) + case saved(highlight: InternalHighlight) case error(errorCode: Enums.CreateHighlightErrorCode) } @@ -54,14 +54,11 @@ public extension DataService { switch payload.data { case let .saved(highlight: highlight): - if let highlightObject = highlight.persist( + _ = highlight.persist( context: self.persistentContainer.viewContext, associatedItemID: articleId - ) { - promise(.success(highlightObject)) - } else { - promise(.failure(.message(messageText: "core data error"))) - } + ) + promise(.success(highlight.encoded())) case let .error(errorCode: errorCode): promise(.failure(.message(messageText: errorCode.rawValue))) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift index 7a762c83d..272046f6c 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift @@ -12,9 +12,9 @@ public extension DataService { patch: String, articleId: String, overlapHighlightIdList: [String] - ) -> AnyPublisher { + ) -> AnyPublisher<[String: Any]?, BasicError> { enum MutationResult { - case saved(highlight: HighlightDep) + case saved(highlight: InternalHighlight) case error(errorCode: Enums.MergeHighlightErrorCode) } @@ -58,14 +58,11 @@ public extension DataService { switch payload.data { case let .saved(highlight: highlight): - if let highlightObject = highlight.persist( + _ = highlight.persist( context: self.persistentContainer.viewContext, associatedItemID: articleId - ) { - promise(.success(highlightObject)) - } else { - promise(.failure(.message(messageText: "core data error"))) - } + ) + promise(.success(highlight.encoded())) case let .error(errorCode: errorCode): promise(.failure(.message(messageText: errorCode.rawValue))) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift index 8bfde44e0..b47768af2 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift @@ -8,9 +8,9 @@ public extension DataService { highlightID: String, annotation: String?, sharedAt: Date? - ) -> AnyPublisher { + ) -> AnyPublisher { enum MutationResult { - case saved(highlight: HighlightDep) + case saved(highlight: InternalHighlight) case error(errorCode: Enums.UpdateHighlightErrorCode) } @@ -48,7 +48,11 @@ public extension DataService { switch payload.data { case let .saved(highlight: highlight): - promise(.success(highlight)) + _ = highlight.persist( + context: self.persistentContainer.viewContext, + associatedItemID: "" // TODO: pass in articleID or just use update core data func + ) + promise(.success(highlight.id)) case let .error(errorCode: errorCode): promise(.failure(.message(messageText: errorCode.rawValue))) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift index 703f8683b..13f29d163 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/ArticleContentQuery.swift @@ -13,7 +13,7 @@ public extension DataService { let articleSelection = Selection.Article { ArticleContentDep( htmlContent: try $0.content(), - highlights: try $0.highlights(selection: highlightSelection.list), + highlights: try $0.highlights(selection: highlightDepSelection.list), storedHighlightsJSONString: nil ) } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Queries/PDFHighlightsQuery.swift b/apple/OmnivoreKit/Sources/Services/DataService/Queries/PDFHighlightsQuery.swift index 7fe2ff470..4e7ee8331 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Queries/PDFHighlightsQuery.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Queries/PDFHighlightsQuery.swift @@ -10,21 +10,8 @@ public extension DataService { case error(error: String) } - let highlightSelection = Selection.Highlight { - HighlightDep( - id: try $0.id(), - shortId: try $0.shortId(), - quote: try $0.quote(), - prefix: try $0.prefix(), - suffix: try $0.suffix(), - patch: try $0.patch(), - annotation: try $0.annotation(), - createdByMe: try $0.createdByMe() - ) - } - let articleSelection = Selection.Article { - try $0.highlights(selection: highlightSelection.list) + try $0.highlights(selection: highlightDepSelection.list) } let selection = Selection { diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Selections/HighlightSelection.swift b/apple/OmnivoreKit/Sources/Services/DataService/Selections/HighlightSelection.swift index 276078550..847eff213 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Selections/HighlightSelection.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Selections/HighlightSelection.swift @@ -2,6 +2,21 @@ import Models import SwiftGraphQL let highlightSelection = Selection.Highlight { + InternalHighlight( + id: try $0.id(), + shortId: try $0.shortId(), + quote: try $0.quote(), + prefix: try $0.prefix(), + suffix: try $0.suffix(), + patch: try $0.patch(), + annotation: try $0.annotation(), + createdAt: try $0.createdAt().value, + updatedAt: try $0.updatedAt().value, + createdByMe: try $0.createdByMe() + ) +} + +let highlightDepSelection = Selection.Highlight { HighlightDep( id: try $0.id(), shortId: try $0.shortId(), diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift new file mode 100644 index 000000000..dd70d59d3 --- /dev/null +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift @@ -0,0 +1,52 @@ +import CoreData +import Foundation +import Models + +struct InternalHighlight: Encodable { + let id: String + let shortId: String + let quote: String + let prefix: String? + let suffix: String? + let patch: String + let annotation: String? + let createdAt: Date? + let updatedAt: Date? + let createdByMe: Bool + + func asManagedObject(context: NSManagedObjectContext, associatedItemID: String) -> Highlight { + let highlight = Highlight(context: context) + highlight.linkedItemId = associatedItemID + highlight.markedForDeletion = false + highlight.id = id + highlight.shortId = shortId + highlight.quote = quote + highlight.prefix = prefix + highlight.suffix = suffix + highlight.patch = patch + highlight.annotation = annotation + highlight.createdAt = createdAt + highlight.updatedAt = updatedAt + highlight.createdByMe = createdByMe + return highlight + } + + func persist(context: NSManagedObjectContext, associatedItemID: String) -> Highlight? { + let highlight = asManagedObject(context: context, associatedItemID: associatedItemID) + + do { + try context.save() + print("Highlight saved succesfully") + return highlight + } catch { + context.rollback() + print("Failed to save Highlight: \(error.localizedDescription)") + return nil + } + } + + func encoded() -> [String: Any]? { + guard let data = try? JSONEncoder().encode(self) else { return nil } + return try? JSONSerialization.jsonObject(with: data, options: .allowFragments) as? [String: Any] + } +} From dd55cd86ae5206ba05eced80d582ab5292ff07f4 Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Tue, 19 Apr 2022 14:55:17 -0700 Subject: [PATCH 24/76] update highlight publishers to make coredata changes --- .../App/PDFSupport/PDFViewerViewModel.swift | 25 +++---------------- .../Mutations/DeleteHighlight.swift | 19 ++++++++++++++ .../Mutations/MergeHighlight.swift | 3 ++- .../Mutations/UpdateHighlightAttributes.swift | 8 +++++- .../InternalModels/InternalHighlight.swift | 14 ++++++++++- .../CachedPDFHighlights.swift | 23 +---------------- 6 files changed, 45 insertions(+), 47 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift index 712d19be3..745e78528 100644 --- a/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift +++ b/apple/OmnivoreKit/Sources/App/PDFSupport/PDFViewerViewModel.swift @@ -32,6 +32,7 @@ public final class PDFViewerViewModel: ObservableObject { .store(in: &subscriptions) } + // TODO: use core data instead private func allHighlights(fetchedHighlights: [HighlightDep]) -> [HighlightDep] { var resultSet = [String: HighlightDep]() @@ -79,6 +80,7 @@ public final class PDFViewerViewModel: ObservableObject { .store(in: &subscriptions) } + // TODO: able to delete this now? public func mergeHighlight( shortId: String, highlightID: String, @@ -86,22 +88,6 @@ public final class PDFViewerViewModel: ObservableObject { patch: String, overlapHighlightIdList: [String] ) { - services.dataService.persistHighlight( - pdfID: feedItem.id, - highlight: HighlightDep( - id: highlightID, - shortId: shortId, - quote: quote, - prefix: nil, - suffix: nil, - patch: patch, - annotation: nil, - createdByMe: true - ) - ) - - removeLocalHighlights(highlightIds: overlapHighlightIdList) - services.dataService .mergeHighlightPublisher( shortId: shortId, @@ -121,8 +107,7 @@ public final class PDFViewerViewModel: ObservableObject { } public func removeHighlights(highlightIds: [String]) { - removeLocalHighlights(highlightIds: highlightIds) - + // TODO: update function to take an array? highlightIds.forEach { highlightId in services.dataService.deleteHighlightPublisher(highlightId: highlightId) .sink { [weak self] completion in @@ -162,8 +147,4 @@ public final class PDFViewerViewModel: ObservableObject { return components?.url } - - private func removeLocalHighlights(highlightIds: [String]) { - services.dataService.removeHighlights(highlightIds: highlightIds) - } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeleteHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeleteHighlight.swift index 60d021949..65f8ce947 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeleteHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/DeleteHighlight.swift @@ -1,4 +1,5 @@ import Combine +import CoreData import Foundation import Models import SwiftGraphQL @@ -42,6 +43,7 @@ public extension DataService { switch payload.data { case let .saved(id: id): + self.deletePersistedHighlight(objectID: id) promise(.success(id)) case let .error(errorCode: errorCode): promise(.failure(.message(messageText: errorCode.rawValue))) @@ -55,4 +57,21 @@ public extension DataService { .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } + + func deletePersistedHighlight(objectID: String) { + let context = persistentContainer.viewContext + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id == %@", objectID) + for highlight in (try? context.fetch(fetchRequest)) ?? [] { + context.delete(highlight) + } + + do { + try context.save() + print("Highlight deleted succesfully") + } catch { + context.rollback() + print("Failed to delete Highlight: \(error.localizedDescription)") + } + } } diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift index 272046f6c..4f8b8d4bf 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/MergeHighlight.swift @@ -60,7 +60,8 @@ public extension DataService { case let .saved(highlight: highlight): _ = highlight.persist( context: self.persistentContainer.viewContext, - associatedItemID: articleId + associatedItemID: articleId, + oldHighlightsIds: overlapHighlightIdList ) promise(.success(highlight.encoded())) case let .error(errorCode: errorCode): diff --git a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift index b47768af2..27754240f 100644 --- a/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift +++ b/apple/OmnivoreKit/Sources/Services/DataService/Mutations/UpdateHighlightAttributes.swift @@ -1,4 +1,5 @@ import Combine +import CoreData import Foundation import Models import SwiftGraphQL @@ -48,9 +49,14 @@ public extension DataService { switch payload.data { case let .saved(highlight: highlight): + let context = self.persistentContainer.viewContext + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id == %@", highlight.id) + let itemID = (try? context.fetch(fetchRequest))?.first?.linkedItemId ?? "" + _ = highlight.persist( context: self.persistentContainer.viewContext, - associatedItemID: "" // TODO: pass in articleID or just use update core data func + associatedItemID: itemID ) promise(.success(highlight.id)) case let .error(errorCode: errorCode): diff --git a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift index dd70d59d3..c856705b3 100644 --- a/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift +++ b/apple/OmnivoreKit/Sources/Services/InternalModels/InternalHighlight.swift @@ -31,9 +31,21 @@ struct InternalHighlight: Encodable { return highlight } - func persist(context: NSManagedObjectContext, associatedItemID: String) -> Highlight? { + func persist( + context: NSManagedObjectContext, + associatedItemID: String, + oldHighlightsIds: [String] = [] + ) -> Highlight? { let highlight = asManagedObject(context: context, associatedItemID: associatedItemID) + if !oldHighlightsIds.isEmpty { + let fetchRequest: NSFetchRequest = Highlight.fetchRequest() + fetchRequest.predicate = NSPredicate(format: "id IN %@", oldHighlightsIds) + for highlight in (try? context.fetch(fetchRequest)) ?? [] { + context.delete(highlight) + } + } + do { try context.save() print("Highlight saved succesfully") diff --git a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift index b036a6b8c..c5f7fc784 100644 --- a/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift +++ b/apple/OmnivoreKit/Sources/Services/Persistence/PersistableModels/CachedPDFHighlights.swift @@ -3,6 +3,7 @@ import CoreData import Foundation import Models +// TODO: possibly remove this file? public extension DataService { func cachedHighlights(pdfID: String) -> [HighlightDep] { let fetchRequest: NSFetchRequest = Highlight.fetchRequest() @@ -28,26 +29,4 @@ public extension DataService { print("Failed to save Highlight: \(error)") } } - - func removeHighlights(highlightIds: [String]) { - for highlightID in highlightIds { - deletedHighlightsIDs.insert(highlightID) - } - - let fetchRequest: NSFetchRequest = Highlight.fetchRequest() - fetchRequest.predicate = NSPredicate(format: "id IN %@", highlightIds) - guard let highlights = try? persistentContainer.viewContext.fetch(fetchRequest) else { return } - - for highlight in highlights { - highlight.markedForDeletion = true - } - - do { - try persistentContainer.viewContext.save() - print("Highlight(s) updated succesfully") - } catch { - persistentContainer.viewContext.rollback() - print("Failed to update Highlight(s): \(error)") - } - } } From 6968e06da50b7b5c3d16e56b109cd455ac7e651f Mon Sep 17 00:00:00 2001 From: Satindar Dhillon Date: Wed, 20 Apr 2022 14:42:27 -0700 Subject: [PATCH 25/76] use persisted article content model to store html and highlights string --- .../App/Views/WebReader/WebReader.swift | 6 ++- .../Views/WebReader/WebReaderContainer.swift | 3 +- .../Views/WebReader/WebReaderContent.swift | 13 +++-- .../CoreDataModel.xcdatamodel/contents | 3 +- .../Models/DataModels/ArticleContentDep.swift | 19 ++------ .../Services/DataService/DataService.swift | 31 ++++++++++-- .../Queries/ArticleContentQuery.swift | 48 ++++++++++++++----- .../InternalModels/InternalHighlight.swift | 23 +++++++++ .../CachedPDFHighlights.swift | 2 +- 9 files changed, 106 insertions(+), 42 deletions(-) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift index e38dd8c95..79b21e94e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReader.swift @@ -6,7 +6,8 @@ import WebKit #if os(iOS) struct WebReader: UIViewRepresentable { - let articleContent: ArticleContentDep + let htmlContent: String + let highlightsJSONString: String let item: FeedItemDep let openLinkAction: (URL) -> Void let webViewActionHandler: (WKScriptMessage, WKScriptMessageReplyHandler?) -> Void @@ -99,7 +100,8 @@ import WebKit func loadContent(webView: WKWebView) { webView.loadHTMLString( WebReaderContent( - articleContent: articleContent, + htmlContent: htmlContent, + highlightsJSONString: highlightsJSONString, item: item, isDark: UITraitCollection.current.userInterfaceStyle == .dark, fontSize: fontSize() diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift index dbfadd324..11d93d20e 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContainer.swift @@ -149,7 +149,8 @@ import WebKit ZStack { if let articleContent = viewModel.articleContent { WebReader( - articleContent: articleContent, + htmlContent: articleContent.htmlContent, + highlightsJSONString: articleContent.highlightsJSONString, item: item, openLinkAction: { #if os(macOS) diff --git a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift index 42a2f1a26..1c47fe344 100644 --- a/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift +++ b/apple/OmnivoreKit/Sources/App/Views/WebReader/WebReaderContent.swift @@ -4,18 +4,21 @@ import Utils struct WebReaderContent { let textFontSize: Int - let articleContent: ArticleContentDep + let htmlContent: String + let highlightsJSONString: String let item: FeedItemDep let themeKey: String init( - articleContent: ArticleContentDep, + htmlContent: String, + highlightsJSONString: String, item: FeedItemDep, isDark: Bool, fontSize: Int ) { self.textFontSize = fontSize - self.articleContent = articleContent + self.htmlContent = htmlContent + self.highlightsJSONString = highlightsJSONString self.item = item self.themeKey = isDark ? "Gray" : "LightGray" } @@ -39,7 +42,7 @@ struct WebReaderContent {