From b54bf7764c4151d042d085a857187e03e5960761 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 14 Feb 2023 16:00:15 -0800 Subject: [PATCH 1/6] feat(coredata): updating coredata and creating initalizers --- .../Item+CoreDataClass.swift | 28 +++++ .../Item+CoreDataProperties.swift | 116 ++++++++++++++++++ .../Sync/CoreDataInitializers/README.md | 19 +++ .../Recommendation+CoreDataClass.swift | 29 +++++ .../Recommendation+CoreDataProperties.swift | 20 +++ .../SavedItem+CoreDataClass.swift | 30 +++++ .../SavedItem+CoreDataProperties.swift | 60 +++++++++ .../Slate+CoreDataClass.swift | 32 +++++ .../Slate+CoreDataProperties.swift | 54 ++++++++ .../SlateLineup+CoreDataClass.swift | 32 +++++ .../SlateLineup+CoreDataProperties.swift | 52 ++++++++ .../PocketModel.xcdatamodel/contents | 32 ++--- 12 files changed, 488 insertions(+), 16 deletions(-) create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataClass.swift create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataProperties.swift create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/README.md create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataClass.swift create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataProperties.swift create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataClass.swift create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataProperties.swift create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataClass.swift create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataProperties.swift create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/SlateLineup+CoreDataClass.swift create mode 100644 PocketKit/Sources/Sync/CoreDataInitializers/SlateLineup+CoreDataProperties.swift diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataClass.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataClass.swift new file mode 100644 index 000000000..212261d27 --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataClass.swift @@ -0,0 +1,28 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +import Foundation +import CoreData + +@objc(Item) +public class Item: NSManagedObject { + @available(*, unavailable) + public init() { + fatalError() + } + + @available(*, unavailable) + public init(context: NSManagedObjectContext) { + fatalError() + } + + public init(context: NSManagedObjectContext, + givenURL: URL, + remoteID: String) { + let entity = NSEntityDescription.entity(forEntityName: "Item", in: context)! + super.init(entity: entity, insertInto: context) + self.givenURL = givenURL + self.remoteID = remoteID + } +} diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataProperties.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataProperties.swift new file mode 100644 index 000000000..81ce7b93d --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataProperties.swift @@ -0,0 +1,116 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation +import CoreData + +extension Item { + @nonobjc + public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Item") + } + + @NSManaged public var article: Article? + @NSManaged public var datePublished: Date? + @NSManaged public var domain: String? + @NSManaged public var excerpt: String? + @NSManaged public var givenURL: URL + @NSManaged public var imageness: String? + @NSManaged public var isArticle: Bool + @NSManaged public var language: String? + @NSManaged public var remoteID: String + @NSManaged public var resolvedURL: URL? + @NSManaged public var timeToRead: NSNumber? + @NSManaged public var title: String? + @NSManaged public var topImageURL: URL? + @NSManaged public var videoness: String? + @NSManaged public var authors: NSOrderedSet? + @NSManaged public var domainMetadata: DomainMetadata? + @NSManaged public var images: NSOrderedSet? + @NSManaged public var recommendations: NSSet? + @NSManaged public var savedItem: SavedItem? + @NSManaged public var syndicatedArticle: SyndicatedArticle? +} + +// MARK: Generated accessors for authors +extension Item { + @objc(insertObject:inAuthorsAtIndex:) + @NSManaged public func insertIntoAuthors(_ value: Author, at idx: Int) + + @objc(removeObjectFromAuthorsAtIndex:) + @NSManaged public func removeFromAuthors(at idx: Int) + + @objc(insertAuthors:atIndexes:) + @NSManaged public func insertIntoAuthors(_ values: [Author], at indexes: NSIndexSet) + + @objc(removeAuthorsAtIndexes:) + @NSManaged public func removeFromAuthors(at indexes: NSIndexSet) + + @objc(replaceObjectInAuthorsAtIndex:withObject:) + @NSManaged public func replaceAuthors(at idx: Int, with value: Author) + + @objc(replaceAuthorsAtIndexes:withAuthors:) + @NSManaged public func replaceAuthors(at indexes: NSIndexSet, with values: [Author]) + + @objc(addAuthorsObject:) + @NSManaged public func addToAuthors(_ value: Author) + + @objc(removeAuthorsObject:) + @NSManaged public func removeFromAuthors(_ value: Author) + + @objc(addAuthors:) + @NSManaged public func addToAuthors(_ values: NSOrderedSet) + + @objc(removeAuthors:) + @NSManaged public func removeFromAuthors(_ values: NSOrderedSet) +} + +// MARK: Generated accessors for images +extension Item { + @objc(insertObject:inImagesAtIndex:) + @NSManaged public func insertIntoImages(_ value: Image, at idx: Int) + + @objc(removeObjectFromImagesAtIndex:) + @NSManaged public func removeFromImages(at idx: Int) + + @objc(insertImages:atIndexes:) + @NSManaged public func insertIntoImages(_ values: [Image], at indexes: NSIndexSet) + + @objc(removeImagesAtIndexes:) + @NSManaged public func removeFromImages(at indexes: NSIndexSet) + + @objc(replaceObjectInImagesAtIndex:withObject:) + @NSManaged public func replaceImages(at idx: Int, with value: Image) + + @objc(replaceImagesAtIndexes:withImages:) + @NSManaged public func replaceImages(at indexes: NSIndexSet, with values: [Image]) + + @objc(addImagesObject:) + @NSManaged public func addToImages(_ value: Image) + + @objc(removeImagesObject:) + @NSManaged public func removeFromImages(_ value: Image) + + @objc(addImages:) + @NSManaged public func addToImages(_ values: NSOrderedSet) + + @objc(removeImages:) + @NSManaged public func removeFromImages(_ values: NSOrderedSet) +} + +// MARK: Generated accessors for recommendations +extension Item { + @objc(addRecommendationsObject:) + @NSManaged public func addToRecommendations(_ value: Recommendation) + + @objc(removeRecommendationsObject:) + @NSManaged public func removeFromRecommendations(_ value: Recommendation) + + @objc(addRecommendations:) + @NSManaged public func addToRecommendations(_ values: NSSet) + + @objc(removeRecommendations:) + @NSManaged public func removeFromRecommendations(_ values: NSSet) +} diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/README.md b/PocketKit/Sources/Sync/CoreDataInitializers/README.md new file mode 100644 index 000000000..edad48d63 --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/README.md @@ -0,0 +1,19 @@ +# Core Data Initializers + +Our CoreData models have some properties that are defined as non-optional (non-nil). However Swift and CoreData do not behave how one would expect. Defining a non-optional field in CoreData will still produce a generated optional in Swift. This then gets even trickier because `@NSManaged` annotations make swift ignore non-nil rules. + +The workaround for this is to define our own initlizers for all CoreData models and not allow the default initializers to compile. This directory contains all the initializers as extensions to our CoreData models to allow non-nil values so we can remove uncessary guard checks in our code. + +These articles explain this problem well: +* Solution: https://tanaschita.com/20221114-how-to-handle-non-optional-core-data-values-in-swift/ +* Other reference: https://www.atomicbird.com/blog/clash-of-the-optionals/ + + +## Generating New Classes + +To generate new classes from the model follow the following steps: + +1. Select the PocketModel in the Project sidebar +2. From the Xcode menu bar, choose Editor > Create NSManagedObject Subclass. +3. Select your data model, then the appropriate entity, and choose where to save the files. Xcode places both a class and a properties file into your project. + diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataClass.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataClass.swift new file mode 100644 index 000000000..93944127d --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataClass.swift @@ -0,0 +1,29 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation +import CoreData + +@objc(Recommendation) +public class Recommendation: NSManagedObject { + @available(*, unavailable) + public init() { + fatalError() + } + + @available(*, unavailable) + public init(context: NSManagedObjectContext) { + fatalError() + } + + public init( + context: NSManagedObjectContext, + remoteID: String + ) { + let entity = NSEntityDescription.entity(forEntityName: "Recommendation", in: context)! + super.init(entity: entity, insertInto: context) + self.remoteID = remoteID + } +} diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataProperties.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataProperties.swift new file mode 100644 index 000000000..bcde69ddb --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataProperties.swift @@ -0,0 +1,20 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +import Foundation +import CoreData + +extension Recommendation { + @nonobjc + public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Recommendation") + } + + @NSManaged public var excerpt: String? + @NSManaged public var imageURL: URL? + @NSManaged public var remoteID: String + @NSManaged public var title: String? + @NSManaged public var item: Item? + @NSManaged public var slate: Slate? +} diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataClass.swift b/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataClass.swift new file mode 100644 index 000000000..0d24621d6 --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataClass.swift @@ -0,0 +1,30 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +import Foundation +import CoreData + +@objc(SavedItem) +public class SavedItem: NSManagedObject { + @available(*, unavailable) + public init() { + fatalError() + } + + @available(*, unavailable) + public init(context: NSManagedObjectContext) { + fatalError() + } + + public init( + context: NSManagedObjectContext, + url: URL, + remoteID: String + ) { + let entity = NSEntityDescription.entity(forEntityName: "SavedItem", in: context)! + super.init(entity: entity, insertInto: context) + self.url = url + self.remoteID = remoteID + } +} diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataProperties.swift b/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataProperties.swift new file mode 100644 index 000000000..a28b95d24 --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataProperties.swift @@ -0,0 +1,60 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation +import CoreData + +extension SavedItem { + @nonobjc + public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "SavedItem") + } + + @NSManaged public var archivedAt: Date? + @NSManaged public var createdAt: Date? + @NSManaged public var cursor: String? + @NSManaged public var deletedAt: Date? + @NSManaged public var isArchived: Bool + @NSManaged public var isFavorite: Bool + @NSManaged public var remoteID: String + @NSManaged public var url: URL + @NSManaged public var item: Item? + @NSManaged public var savedItemUpdatedNotification: SavedItemUpdatedNotification? + @NSManaged public var tags: NSOrderedSet? + @NSManaged public var unresolvedSavedItem: UnresolvedSavedItem? +} + +// MARK: Generated accessors for tags +extension SavedItem { + @objc(insertObject:inTagsAtIndex:) + @NSManaged public func insertIntoTags(_ value: Tag, at idx: Int) + + @objc(removeObjectFromTagsAtIndex:) + @NSManaged public func removeFromTags(at idx: Int) + + @objc(insertTags:atIndexes:) + @NSManaged public func insertIntoTags(_ values: [Tag], at indexes: NSIndexSet) + + @objc(removeTagsAtIndexes:) + @NSManaged public func removeFromTags(at indexes: NSIndexSet) + + @objc(replaceObjectInTagsAtIndex:withObject:) + @NSManaged public func replaceTags(at idx: Int, with value: Tag) + + @objc(replaceTagsAtIndexes:withTags:) + @NSManaged public func replaceTags(at indexes: NSIndexSet, with values: [Tag]) + + @objc(addTagsObject:) + @NSManaged public func addToTags(_ value: Tag) + + @objc(removeTagsObject:) + @NSManaged public func removeFromTags(_ value: Tag) + + @objc(addTags:) + @NSManaged public func addToTags(_ values: NSOrderedSet) + + @objc(removeTags:) + @NSManaged public func removeFromTags(_ values: NSOrderedSet) +} diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataClass.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataClass.swift new file mode 100644 index 000000000..9770c4fac --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataClass.swift @@ -0,0 +1,32 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +import Foundation +import CoreData + +@objc(Slate) +public class Slate: NSManagedObject { + @available(*, unavailable) + public init() { + fatalError() + } + + @available(*, unavailable) + public init(context: NSManagedObjectContext) { + fatalError() + } + + public init( + context: NSManagedObjectContext, + remoteID: String, + expermimentID: String, + requestID: String + ) { + let entity = NSEntityDescription.entity(forEntityName: "Slate", in: context)! + super.init(entity: entity, insertInto: context) + self.remoteID = remoteID + self.experimentID = expermimentID + self.requestID = requestID + } +} diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataProperties.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataProperties.swift new file mode 100644 index 000000000..448320011 --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataProperties.swift @@ -0,0 +1,54 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +import Foundation +import CoreData + +extension Slate { + @nonobjc + public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "Slate") + } + + @NSManaged public var experimentID: String + @NSManaged public var name: String? + @NSManaged public var remoteID: String + @NSManaged public var requestID: String + @NSManaged public var slateDescription: String? + @NSManaged public var recommendations: NSOrderedSet? + @NSManaged public var slateLineup: SlateLineup? +} + +// MARK: Generated accessors for recommendations +extension Slate { + @objc(insertObject:inRecommendationsAtIndex:) + @NSManaged public func insertIntoRecommendations(_ value: Recommendation, at idx: Int) + + @objc(removeObjectFromRecommendationsAtIndex:) + @NSManaged public func removeFromRecommendations(at idx: Int) + + @objc(insertRecommendations:atIndexes:) + @NSManaged public func insertIntoRecommendations(_ values: [Recommendation], at indexes: NSIndexSet) + + @objc(removeRecommendationsAtIndexes:) + @NSManaged public func removeFromRecommendations(at indexes: NSIndexSet) + + @objc(replaceObjectInRecommendationsAtIndex:withObject:) + @NSManaged public func replaceRecommendations(at idx: Int, with value: Recommendation) + + @objc(replaceRecommendationsAtIndexes:withRecommendations:) + @NSManaged public func replaceRecommendations(at indexes: NSIndexSet, with values: [Recommendation]) + + @objc(addRecommendationsObject:) + @NSManaged public func addToRecommendations(_ value: Recommendation) + + @objc(removeRecommendationsObject:) + @NSManaged public func removeFromRecommendations(_ value: Recommendation) + + @objc(addRecommendations:) + @NSManaged public func addToRecommendations(_ values: NSOrderedSet) + + @objc(removeRecommendations:) + @NSManaged public func removeFromRecommendations(_ values: NSOrderedSet) +} diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/SlateLineup+CoreDataClass.swift b/PocketKit/Sources/Sync/CoreDataInitializers/SlateLineup+CoreDataClass.swift new file mode 100644 index 000000000..bdbefe4aa --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/SlateLineup+CoreDataClass.swift @@ -0,0 +1,32 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// +import Foundation +import CoreData + +@objc(SlateLineup) +public class SlateLineup: NSManagedObject { + @available(*, unavailable) + public init() { + fatalError() + } + + @available(*, unavailable) + public init(context: NSManagedObjectContext) { + fatalError() + } + + public init( + context: NSManagedObjectContext, + remoteID: String, + expermimentID: String, + requestID: String + ) { + let entity = NSEntityDescription.entity(forEntityName: "SlateLineup", in: context)! + super.init(entity: entity, insertInto: context) + self.remoteID = remoteID + self.experimentID = expermimentID + self.requestID = requestID + } +} diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/SlateLineup+CoreDataProperties.swift b/PocketKit/Sources/Sync/CoreDataInitializers/SlateLineup+CoreDataProperties.swift new file mode 100644 index 000000000..d4ff22af0 --- /dev/null +++ b/PocketKit/Sources/Sync/CoreDataInitializers/SlateLineup+CoreDataProperties.swift @@ -0,0 +1,52 @@ +// This Source Code Form is subject to the terms of the Mozilla Public +// License, v. 2.0. If a copy of the MPL was not distributed with this +// file, You can obtain one at http://mozilla.org/MPL/2.0/. +// + +import Foundation +import CoreData + +extension SlateLineup { + @nonobjc + public class func fetchRequest() -> NSFetchRequest { + return NSFetchRequest(entityName: "SlateLineup") + } + + @NSManaged public var experimentID: String + @NSManaged public var remoteID: String + @NSManaged public var requestID: String + @NSManaged public var slates: NSOrderedSet? +} + +// MARK: Generated accessors for slates +extension SlateLineup { + @objc(insertObject:inSlatesAtIndex:) + @NSManaged public func insertIntoSlates(_ value: Slate, at idx: Int) + + @objc(removeObjectFromSlatesAtIndex:) + @NSManaged public func removeFromSlates(at idx: Int) + + @objc(insertSlates:atIndexes:) + @NSManaged public func insertIntoSlates(_ values: [Slate], at indexes: NSIndexSet) + + @objc(removeSlatesAtIndexes:) + @NSManaged public func removeFromSlates(at indexes: NSIndexSet) + + @objc(replaceObjectInSlatesAtIndex:withObject:) + @NSManaged public func replaceSlates(at idx: Int, with value: Slate) + + @objc(replaceSlatesAtIndexes:withSlates:) + @NSManaged public func replaceSlates(at indexes: NSIndexSet, with values: [Slate]) + + @objc(addSlatesObject:) + @NSManaged public func addToSlates(_ value: Slate) + + @objc(removeSlatesObject:) + @NSManaged public func removeFromSlates(_ value: Slate) + + @objc(addSlates:) + @NSManaged public func addToSlates(_ values: NSOrderedSet) + + @objc(removeSlates:) + @NSManaged public func removeFromSlates(_ values: NSOrderedSet) +} diff --git a/PocketKit/Sources/Sync/PocketModel.xcdatamodeld/PocketModel.xcdatamodel/contents b/PocketKit/Sources/Sync/PocketModel.xcdatamodeld/PocketModel.xcdatamodel/contents index 2bc0f1266..fdd9f0f54 100644 --- a/PocketKit/Sources/Sync/PocketModel.xcdatamodeld/PocketModel.xcdatamodel/contents +++ b/PocketKit/Sources/Sync/PocketModel.xcdatamodeld/PocketModel.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -16,12 +16,12 @@ - + - + @@ -42,23 +42,23 @@ - + - + - + - - + + @@ -67,19 +67,19 @@ - - + + - - + + - - - - + + + + From e6bb8684680c4a89f735d51ec22b940a2559ed95 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 14 Feb 2023 17:46:24 -0800 Subject: [PATCH 2/6] chore(null): fixing all base use cases of null --- .../RemoteMapping/Item+remoteMapping.swift | 20 ++++++++--- .../SavedItem+RemoteMapping.swift | 34 +++++++++++++++---- 2 files changed, 43 insertions(+), 11 deletions(-) diff --git a/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift b/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift index a658bf23b..c487a0924 100644 --- a/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift +++ b/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift @@ -6,13 +6,19 @@ import PocketGraph extension Item { func update(remote: ItemParts) { remoteID = remote.remoteID - givenURL = URL(string: remote.givenUrl) + + guard let url = URL(string: remote.givenUrl) else { + //TODO: Daniel log an error. + return + } + + givenURL = url resolvedURL = remote.resolvedUrl.flatMap(URL.init) title = remote.title topImageURL = remote.topImageUrl.flatMap(URL.init) domain = remote.domain language = remote.language - timeToRead = remote.timeToRead.flatMap(Int32.init) ?? 0 + timeToRead = remote.timeToRead.flatMap(NSNumber.init) ?? 0 excerpt = remote.excerpt datePublished = remote.datePublished.flatMap { DateFormatter.clientAPI.date(from: $0) } isArticle = remote.isArticle ?? false @@ -63,13 +69,19 @@ extension Item { func update(from summary: ItemSummary) { remoteID = summary.remoteID - givenURL = URL(string: summary.givenUrl) + + guard let url = URL(string: summary.givenUrl) else { + //TODO: Daniel log an error. + return + } + + givenURL = url resolvedURL = summary.resolvedUrl.flatMap(URL.init) title = summary.title topImageURL = summary.topImageUrl.flatMap(URL.init) domain = summary.domain language = summary.language - timeToRead = summary.timeToRead.flatMap(Int32.init) ?? 0 + timeToRead = summary.timeToRead.flatMap(NSNumber.init) ?? 0 excerpt = summary.excerpt datePublished = summary.datePublished.flatMap { DateFormatter.clientAPI.date(from: $0) } isArticle = summary.isArticle ?? false diff --git a/PocketKit/Sources/Sync/RemoteMapping/SavedItem+RemoteMapping.swift b/PocketKit/Sources/Sync/RemoteMapping/SavedItem+RemoteMapping.swift index f093e34d3..59727d118 100644 --- a/PocketKit/Sources/Sync/RemoteMapping/SavedItem+RemoteMapping.swift +++ b/PocketKit/Sources/Sync/RemoteMapping/SavedItem+RemoteMapping.swift @@ -23,7 +23,13 @@ extension SavedItem { public func update(from remote: RemoteSavedItem, with space: Space) { remoteID = remote.remoteID - url = URL(string: remote.url) + + guard let url = URL(string: remote.url) else { + //TODO: Daniel log an error. + return + } + + self.url = url createdAt = Date(timeIntervalSince1970: TimeInterval(remote._createdAt)) deletedAt = remote._deletedAt.flatMap(TimeInterval.init).flatMap(Date.init(timeIntervalSince1970:)) archivedAt = remote.archivedAt.flatMap(TimeInterval.init).flatMap(Date.init(timeIntervalSince1970:)) @@ -31,7 +37,9 @@ extension SavedItem { isFavorite = remote.isFavorite guard let context = managedObjectContext, - let itemParts = remote.item.asItem?.fragments.itemParts else { + let itemParts = remote.item.asItem?.fragments.itemParts, + let itemUrl = URL(string: itemParts.givenUrl) + else { return } @@ -47,13 +55,18 @@ extension SavedItem { let fetchRequest = Requests.fetchItem(byRemoteID: itemParts.remoteID) fetchRequest.fetchLimit = 1 - let itemToUpdate = try? context.fetch(fetchRequest).first ?? Item(context: context) + let itemToUpdate = try? context.fetch(fetchRequest).first ?? Item(context: context, givenURL: itemUrl, remoteID: itemParts.remoteID) itemToUpdate?.update(remote: itemParts) item = itemToUpdate } public func update(from recommendation: Recommendation) { - self.url = recommendation.item?.bestURL + guard let url = recommendation.item?.bestURL else { + //TODO: Daniel log an error. + return + } + + self.url = url self.createdAt = Date() item = recommendation.item @@ -61,7 +74,12 @@ extension SavedItem { public func update(from summary: SavedItemSummary, with space: Space) { remoteID = summary.remoteID - url = URL(string: summary.url) + guard let url = URL(string: summary.url) else { + //TODO: Daniel log an error. + return + } + + self.url = url createdAt = Date(timeIntervalSince1970: TimeInterval(summary._createdAt)) deletedAt = summary._deletedAt.flatMap(TimeInterval.init).flatMap(Date.init(timeIntervalSince1970:)) archivedAt = summary.archivedAt.flatMap(TimeInterval.init).flatMap(Date.init(timeIntervalSince1970:)) @@ -69,7 +87,9 @@ extension SavedItem { isFavorite = summary.isFavorite guard let context = managedObjectContext, - let itemSummary = summary.item.asItem?.fragments.itemSummary else { + let itemSummary = summary.item.asItem?.fragments.itemSummary, + let itemUrl = URL(string: itemSummary.givenUrl) + else { return } @@ -83,7 +103,7 @@ extension SavedItem { let fetchRequest = Requests.fetchItem(byRemoteID: itemSummary.remoteID) fetchRequest.fetchLimit = 1 - let itemToUpdate = try? context.fetch(fetchRequest).first ?? Item(context: context) + let itemToUpdate = try? context.fetch(fetchRequest).first ?? Item(context: context, givenURL: itemUrl, remoteID: itemSummary.remoteID) itemToUpdate?.update(from: itemSummary) item = itemToUpdate } From f3c1d78dc265df4f6e03a80a273e1d3838aee126 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 14 Feb 2023 17:46:29 -0800 Subject: [PATCH 3/6] chore(null): fixing all base use cases of null --- .../HomeRecommendationCellViewModel.swift | 2 +- .../PocketKit/Home/HomeViewModel.swift | 32 ++++------ .../PocketKit/Home/SlateDetailViewModel.swift | 36 ++++------- .../SavedItem+ItemsListItem.swift | 2 +- .../Item+CoreDataProperties.swift | 17 +---- .../Sources/Sync/PocketSaveService.swift | 9 ++- PocketKit/Sources/Sync/PocketSource.swift | 64 +++++-------------- .../SlateLineup+remoteMapping.swift | 7 +- 8 files changed, 55 insertions(+), 114 deletions(-) diff --git a/PocketKit/Sources/PocketKit/Home/HomeRecommendationCellViewModel.swift b/PocketKit/Sources/PocketKit/Home/HomeRecommendationCellViewModel.swift index 6ca17e68c..0451f0b37 100644 --- a/PocketKit/Sources/PocketKit/Home/HomeRecommendationCellViewModel.swift +++ b/PocketKit/Sources/PocketKit/Home/HomeRecommendationCellViewModel.swift @@ -56,7 +56,7 @@ extension HomeRecommendationCellViewModel: RecommendationCellViewModel { var timeToRead: String? { guard let timeToRead = recommendation.item?.timeToRead, - timeToRead > 0 else { + timeToRead.intValue > 0 else { return nil } diff --git a/PocketKit/Sources/PocketKit/Home/HomeViewModel.swift b/PocketKit/Sources/PocketKit/Home/HomeViewModel.swift index e7ef97808..fc34952d2 100644 --- a/PocketKit/Sources/PocketKit/Home/HomeViewModel.swift +++ b/PocketKit/Sources/PocketKit/Home/HomeViewModel.swift @@ -593,36 +593,28 @@ extension HomeViewModel { var contexts: [Context] = [] // SlateLineup Context - if let id = slateLineup.remoteID, - let requestID = slateLineup.requestID, - let experimentID = slateLineup.experimentID { - let context = SlateLineupContext( - id: id, - requestID: requestID, - experiment: experimentID - ) - contexts.append(context) - } + let context = SlateLineupContext( + id: slateLineup.remoteID, + requestID: slateLineup.requestID, + experiment: slateLineup.experimentID + ) + contexts.append(context) // Slate context - if let slateID = slate.remoteID, - let requestID = slate.requestID, - let experimentID = slate.experimentID, - let slateIndex = slateLineup.slates?.index(of: slate) { + if let slateIndex = slateLineup.slates?.index(of: slate) { let slateContext = SlateContext( - id: slateID, - requestID: requestID, - experiment: experimentID, + id: slate.remoteID, + requestID: slate.requestID, + experiment: slate.experimentID, index: UIIndex(slateIndex) ) contexts.append(slateContext) } // Recommendation context - if let recommendationID = recommendation.remoteID, - let recommendationIndex = slate.recommendations?.index(of: recommendation) { + if let recommendationIndex = slate.recommendations?.index(of: recommendation) { let recommendationContext = RecommendationContext( - id: recommendationID, + id: recommendation.remoteID, index: UIIndex(recommendationIndex) ) contexts.append(recommendationContext) diff --git a/PocketKit/Sources/PocketKit/Home/SlateDetailViewModel.swift b/PocketKit/Sources/PocketKit/Home/SlateDetailViewModel.swift index 0bb8db853..a5ccd93fa 100644 --- a/PocketKit/Sources/PocketKit/Home/SlateDetailViewModel.swift +++ b/PocketKit/Sources/PocketKit/Home/SlateDetailViewModel.swift @@ -57,12 +57,8 @@ class SlateDetailViewModel { } func refresh(_ completion: @escaping () -> Void) { - guard let slateID = slate.remoteID else { - return - } - Task { - try await source.fetchSlate(slateID) + try await source.fetchSlate(slate.remoteID) completion() } } @@ -201,25 +197,19 @@ extension SlateDetailViewModel { var contexts: [Context] = [] - if let remoteID = slate.remoteID, - let requestID = slate.requestID, - let experimentID = slate.experimentID { - let slateContext = SlateContext( - id: remoteID, - requestID: requestID, - experiment: experimentID, - index: UIIndex(0) - ) - contexts.append(slateContext) - } + let slateContext = SlateContext( + id: slate.remoteID, + requestID: slate.requestID, + experiment: slate.experimentID, + index: UIIndex(0) + ) + contexts.append(slateContext) - if let remoteID = recommendation.remoteID { - let recommendationContext = RecommendationContext( - id: remoteID, - index: UIIndex(indexPath.item) - ) - contexts.append(recommendationContext) - } + let recommendationContext = RecommendationContext( + id: recommendation.remoteID, + index: UIIndex(indexPath.item) + ) + contexts.append(recommendationContext) return contexts + [ ContentContext(url: recommendationURL), diff --git a/PocketKit/Sources/PocketKit/MyList/SavedItemsList/SavedItem+ItemsListItem.swift b/PocketKit/Sources/PocketKit/MyList/SavedItemsList/SavedItem+ItemsListItem.swift index bac4b3b59..57466cb94 100644 --- a/PocketKit/Sources/PocketKit/MyList/SavedItemsList/SavedItem+ItemsListItem.swift +++ b/PocketKit/Sources/PocketKit/MyList/SavedItemsList/SavedItem+ItemsListItem.swift @@ -19,7 +19,7 @@ extension SavedItem: ItemsListItem { } var timeToRead: Int? { - item.flatMap { Int($0.timeToRead) } + item?.timeToRead?.intValue } var domainMetadata: ItemsListItemDomainMetadata? { diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataProperties.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataProperties.swift index 81ce7b93d..943cefe47 100644 --- a/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataProperties.swift +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataProperties.swift @@ -29,7 +29,7 @@ extension Item { @NSManaged public var authors: NSOrderedSet? @NSManaged public var domainMetadata: DomainMetadata? @NSManaged public var images: NSOrderedSet? - @NSManaged public var recommendations: NSSet? + @NSManaged public var recommendation: Recommendation? @NSManaged public var savedItem: SavedItem? @NSManaged public var syndicatedArticle: SyndicatedArticle? } @@ -99,18 +99,3 @@ extension Item { @objc(removeImages:) @NSManaged public func removeFromImages(_ values: NSOrderedSet) } - -// MARK: Generated accessors for recommendations -extension Item { - @objc(addRecommendationsObject:) - @NSManaged public func addToRecommendations(_ value: Recommendation) - - @objc(removeRecommendationsObject:) - @NSManaged public func removeFromRecommendations(_ value: Recommendation) - - @objc(addRecommendations:) - @NSManaged public func addToRecommendations(_ values: NSSet) - - @objc(removeRecommendations:) - @NSManaged public func removeFromRecommendations(_ values: NSSet) -} diff --git a/PocketKit/Sources/Sync/PocketSaveService.swift b/PocketKit/Sources/Sync/PocketSaveService.swift index ee903d047..c45146675 100644 --- a/PocketKit/Sources/Sync/PocketSaveService.swift +++ b/PocketKit/Sources/Sync/PocketSaveService.swift @@ -101,11 +101,11 @@ public class PocketSaveService: SaveService { return } - guard let tags = savedItem.tags, let remoteID = savedItem.remoteID else { return } + guard let tags = savedItem.tags else { return } let names = Array(tags).compactMap { ($0 as? Tag)?.name } if names.isEmpty { - let mutation = UpdateSavedItemRemoveTagsMutation(savedItemId: remoteID) + let mutation = UpdateSavedItemRemoveTagsMutation(savedItemId: savedItem.remoteID) let operation = SaveOperation( apollo: apollo, @@ -119,7 +119,7 @@ public class PocketSaveService: SaveService { queue.addOperation(operation) queue.waitUntilAllOperationsAreFinished() } else { - let mutation = ReplaceSavedItemTagsMutation(input: [SavedItemTagsInput(savedItemId: remoteID, tags: names)]) + let mutation = ReplaceSavedItemTagsMutation(input: [SavedItemTagsInput(savedItemId: savedItem.remoteID, tags: names)]) let operation = SaveOperation( apollo: apollo, @@ -142,8 +142,7 @@ public class PocketSaveService: SaveService { return } - guard let url = savedItem.url else { return } - let mutation = SaveItemMutation(input: SavedItemUpsertInput(url: url.absoluteString)) + let mutation = SaveItemMutation(input: SavedItemUpsertInput(url: savedItem.url.absoluteString)) let operation = SaveOperation( apollo: apollo, diff --git a/PocketKit/Sources/Sync/PocketSource.swift b/PocketKit/Sources/Sync/PocketSource.swift index 489368ae7..a4029793f 100644 --- a/PocketKit/Sources/Sync/PocketSource.swift +++ b/PocketKit/Sources/Sync/PocketSource.swift @@ -208,42 +208,31 @@ extension PocketSource { } public func favorite(item: SavedItem) { - guard let remoteID = item.remoteID else { - return - } - item.isFavorite = true try? space.save() let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: FavoriteItemMutation(itemID: remoteID) + mutation: FavoriteItemMutation(itemID: item.remoteID) ) - enqueue(operation: operation, task: .favorite(remoteID: remoteID)) + enqueue(operation: operation, task: .favorite(remoteID: item.remoteID)) } public func unfavorite(item: SavedItem) { - guard let remoteID = item.remoteID else { - return - } - item.isFavorite = false try? space.save() let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: UnfavoriteItemMutation(itemID: remoteID) + mutation: UnfavoriteItemMutation(itemID: item.remoteID) ) - enqueue(operation: operation, task: .unfavorite(remoteID: remoteID)) + enqueue(operation: operation, task: .unfavorite(remoteID: item.remoteID)) } public func delete(item savedItem: SavedItem) { - guard let remoteID = savedItem.remoteID else { - return - } let item = savedItem.item @@ -258,17 +247,13 @@ extension PocketSource { let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: DeleteItemMutation(itemID: remoteID) + mutation: DeleteItemMutation(itemID: savedItem.remoteID) ) - enqueue(operation: operation, task: .delete(remoteID: remoteID)) + enqueue(operation: operation, task: .delete(remoteID: savedItem.remoteID)) } public func archive(item: SavedItem) { - guard let remoteID = item.remoteID else { - return - } - item.isArchived = true item.archivedAt = Date() try? space.save() @@ -276,50 +261,40 @@ extension PocketSource { let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: ArchiveItemMutation(itemID: remoteID) + mutation: ArchiveItemMutation(itemID: item.remoteID) ) - enqueue(operation: operation, task: .archive(remoteID: remoteID)) + enqueue(operation: operation, task: .archive(remoteID: item.remoteID)) } public func unarchive(item: SavedItem) { - guard let url = item.url else { return } - item.isArchived = false item.createdAt = Date() try? space.save() let operation = operations.saveItemOperation( managedItemID: item.objectID, - url: url, + url: item.url, events: _events, apollo: apollo, space: space ) - enqueue(operation: operation, task: .save(localID: item.objectID.uriRepresentation(), url: url)) + enqueue(operation: operation, task: .save(localID: item.objectID.uriRepresentation(), url: item.url)) } public func save(item: SavedItem) { - guard let url = item.url else { - return - } - let operation = operations.saveItemOperation( managedItemID: item.objectID, - url: url, + url: item.url, events: _events, apollo: apollo, space: space ) - enqueue(operation: operation, task: .save(localID: item.objectID.uriRepresentation(), url: url)) + enqueue(operation: operation, task: .save(localID: item.objectID.uriRepresentation(), url: item.url)) } public func addTags(item: SavedItem, tags: [String]) { - guard let remoteID = item.remoteID else { - return - } - item.tags = NSOrderedSet(array: tags.compactMap { $0 }.map({ tag in space.fetchOrCreateTag(byName: tag) })) @@ -329,10 +304,10 @@ extension PocketSource { let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: getMutation(for: tags, and: remoteID) + mutation: getMutation(for: tags, and: item.remoteID) ) - enqueue(operation: operation, task: .addTags(remoteID: remoteID, tags: tags)) + enqueue(operation: operation, task: .addTags(remoteID: item.remoteID, tags: tags)) } public func deleteTag(tag: Tag) { @@ -379,12 +354,8 @@ extension PocketSource { } public func fetchDetails(for savedItem: SavedItem) async throws { - guard let remoteID = savedItem.remoteID else { - return - } - guard let remoteSavedItem = try await apollo - .fetch(query: SavedItemByIDQuery(id: remoteID)) + .fetch(query: SavedItemByIDQuery(id: savedItem.remoteID)) .data?.user?.savedItemById else { return } @@ -421,13 +392,12 @@ extension PocketSource { } public func fetchDetails(for recommendation: Recommendation) async throws { - guard let item = recommendation.item, - let remoteID = item.remoteID else { + guard let item = recommendation.item else { return } guard let remoteItem = try await apollo - .fetch(query: ItemByIDQuery(id: remoteID)) + .fetch(query: ItemByIDQuery(id: recommendation.remoteID)) .data?.itemByItemId?.fragments.itemParts else { return } diff --git a/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift b/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift index 3fa558e17..3f772aa11 100644 --- a/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift +++ b/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift @@ -44,7 +44,12 @@ extension Recommendation { public typealias RemoteRecommendation = SlateParts.Recommendation func update(from remote: RemoteRecommendation, in space: Space) { - remoteID = remote.id + guard let id = remote.id else { + //TODO: Daniel log, also daniel work to make this non-null in the API. + return + } + + remoteID = id title = remote.curatedInfo?.title excerpt = remote.curatedInfo?.excerpt imageURL = remote.curatedInfo?.imageSrc.flatMap(URL.init) From 8dd92ec3fa29ef3d632decde55b1533451f3586a Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Tue, 14 Feb 2023 17:53:49 -0800 Subject: [PATCH 4/6] feat(new): removing uses of new --- .../SavedItem+CoreDataClass.swift | 2 +- .../SavedItem+CoreDataProperties.swift | 2 +- .../Sync/Operations/ArchiveService.swift | 5 +- .../Sources/Sync/Operations/FetchList.swift | 4 +- .../PocketModel.xcdatamodel/contents | 4 +- .../Sources/Sync/PocketSaveService.swift | 14 ++-- PocketKit/Sources/Sync/PocketSource.swift | 64 +++++++++++++------ .../SlateLineup+remoteMapping.swift | 10 +-- .../Sources/Sync/Slates/SlateService.swift | 4 +- PocketKit/Sources/Sync/Space.swift | 24 ------- .../ArchivedItemsListViewModelTests.swift | 2 +- .../PocketAddTagsViewModelTests.swift | 5 +- .../RecommendationViewModelTests.swift | 4 +- .../SavedItemViewModelTests.swift | 4 +- .../SavedItemsListViewModelTests.swift | 4 +- .../Support/Space+factories.swift | 34 +++------- .../SaveToAddTagsViewModelTests.swift | 4 +- .../SavedItemViewModelTests.swift | 6 +- .../Support/Space+factories.swift | 32 +++------- .../SyncTests/APISlateServiceTests.swift | 10 +-- .../Operations/SaveItemOperationTests.swift | 6 +- .../SyncTests/PocketSaveServiceTests.swift | 4 +- .../PocketSourceTests+savedItemEvents.swift | 8 +-- .../Tests/SyncTests/PocketSourceTests.swift | 12 ++-- PocketKit/Tests/SyncTests/SpaceTests.swift | 8 +-- .../SyncTests/Support/Space+factories.swift | 26 +++----- 26 files changed, 130 insertions(+), 172 deletions(-) diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataClass.swift b/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataClass.swift index 0d24621d6..3b4f008e7 100644 --- a/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataClass.swift +++ b/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataClass.swift @@ -20,7 +20,7 @@ public class SavedItem: NSManagedObject { public init( context: NSManagedObjectContext, url: URL, - remoteID: String + remoteID: String? = nil ) { let entity = NSEntityDescription.entity(forEntityName: "SavedItem", in: context)! super.init(entity: entity, insertInto: context) diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataProperties.swift b/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataProperties.swift index a28b95d24..ead89c152 100644 --- a/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataProperties.swift +++ b/PocketKit/Sources/Sync/CoreDataInitializers/SavedItem+CoreDataProperties.swift @@ -18,7 +18,7 @@ extension SavedItem { @NSManaged public var deletedAt: Date? @NSManaged public var isArchived: Bool @NSManaged public var isFavorite: Bool - @NSManaged public var remoteID: String + @NSManaged public var remoteID: String? @NSManaged public var url: URL @NSManaged public var item: Item? @NSManaged public var savedItemUpdatedNotification: SavedItemUpdatedNotification? diff --git a/PocketKit/Sources/Sync/Operations/ArchiveService.swift b/PocketKit/Sources/Sync/Operations/ArchiveService.swift index 7dc44d2f5..acd0438a7 100644 --- a/PocketKit/Sources/Sync/Operations/ArchiveService.swift +++ b/PocketKit/Sources/Sync/Operations/ArchiveService.swift @@ -257,11 +257,12 @@ extension PocketArchiveService: FetchArchivePagesOperationDelegate { for edge in edges { guard let edge = edge, - let summary = edge.node?.fragments.savedItemSummary else { + let summary = edge.node?.fragments.savedItemSummary, + let url = URL(string: summary.url) else { continue } - let savedItem = try space.fetchOrCreateSavedItem(byRemoteID: summary.remoteID) + let savedItem = (try? space.fetchSavedItem(byRemoteID: summary.remoteID)) ?? SavedItem(context: space.context, url: url, remoteID: summary.remoteID) savedItem.cursor = edge.cursor savedItem.update(from: summary, with: space) diff --git a/PocketKit/Sources/Sync/Operations/FetchList.swift b/PocketKit/Sources/Sync/Operations/FetchList.swift index 03d34b3c1..8ff258e19 100644 --- a/PocketKit/Sources/Sync/Operations/FetchList.swift +++ b/PocketKit/Sources/Sync/Operations/FetchList.swift @@ -136,7 +136,7 @@ class FetchList: SyncOperation { } for edge in edges { - guard let edge = edge, let node = edge.node else { + guard let edge = edge, let node = edge.node, let url = URL(string: node.url) else { return } @@ -146,7 +146,7 @@ class FetchList: SyncOperation { message: "Updating/Inserting SavedItem with ID: \(node.remoteID)" ) - let item = try space.fetchOrCreateSavedItem(byRemoteID: node.remoteID) + let item = (try? space.fetchSavedItem(byRemoteID: node.remoteID)) ?? SavedItem(context: space.context, url: url, remoteID: node.remoteID) item.update(from: edge, with: space) if item.deletedAt != nil { diff --git a/PocketKit/Sources/Sync/PocketModel.xcdatamodeld/PocketModel.xcdatamodel/contents b/PocketKit/Sources/Sync/PocketModel.xcdatamodeld/PocketModel.xcdatamodel/contents index fdd9f0f54..a69038521 100644 --- a/PocketKit/Sources/Sync/PocketModel.xcdatamodeld/PocketModel.xcdatamodel/contents +++ b/PocketKit/Sources/Sync/PocketModel.xcdatamodeld/PocketModel.xcdatamodel/contents @@ -1,5 +1,5 @@ - + @@ -57,7 +57,7 @@ - + diff --git a/PocketKit/Sources/Sync/PocketSaveService.swift b/PocketKit/Sources/Sync/PocketSaveService.swift index c45146675..d92bfb134 100644 --- a/PocketKit/Sources/Sync/PocketSaveService.swift +++ b/PocketKit/Sources/Sync/PocketSaveService.swift @@ -76,7 +76,7 @@ public class PocketSaveService: SaveService { if let existingItem = try! space.fetchSavedItem(byURL: url) { existingItem.createdAt = Date() - let notification: SavedItemUpdatedNotification = space.new() + let notification: SavedItemUpdatedNotification = SavedItemUpdatedNotification(context: space.context) notification.savedItem = existingItem try? space.save() @@ -84,7 +84,7 @@ public class PocketSaveService: SaveService { osNotifications.post(name: .savedItemUpdated) return .existingItem(existingItem) } else { - let savedItem: SavedItem = space.new() + let savedItem: SavedItem = SavedItem(context: space.context, url: url) savedItem.url = url savedItem.createdAt = Date() try? space.save() @@ -101,11 +101,11 @@ public class PocketSaveService: SaveService { return } - guard let tags = savedItem.tags else { return } + guard let tags = savedItem.tags, let remoteID = savedItem.remoteID else { return } let names = Array(tags).compactMap { ($0 as? Tag)?.name } if names.isEmpty { - let mutation = UpdateSavedItemRemoveTagsMutation(savedItemId: savedItem.remoteID) + let mutation = UpdateSavedItemRemoveTagsMutation(savedItemId: remoteID) let operation = SaveOperation( apollo: apollo, @@ -119,7 +119,7 @@ public class PocketSaveService: SaveService { queue.addOperation(operation) queue.waitUntilAllOperationsAreFinished() } else { - let mutation = ReplaceSavedItemTagsMutation(input: [SavedItemTagsInput(savedItemId: savedItem.remoteID, tags: names)]) + let mutation = ReplaceSavedItemTagsMutation(input: [SavedItemTagsInput(savedItemId: remoteID, tags: names)]) let operation = SaveOperation( apollo: apollo, @@ -213,7 +213,7 @@ class SaveOperation: AsyncOperation { private func updateSavedItem(savedItemParts: SavedItemParts) { savedItem.update(from: savedItemParts, with: space) - let notification: SavedItemUpdatedNotification = space.new() + let notification: SavedItemUpdatedNotification = SavedItemUpdatedNotification(context: space.context) notification.savedItem = savedItem try? space.save() @@ -223,7 +223,7 @@ class SaveOperation: AsyncOperation { private func storeUnresolvedSavedItem() { try? space.context.performAndWait { - let unresolved: UnresolvedSavedItem = space.new() + let unresolved: UnresolvedSavedItem = UnresolvedSavedItem(context: space.context) unresolved.savedItem = savedItem try space.save() } diff --git a/PocketKit/Sources/Sync/PocketSource.swift b/PocketKit/Sources/Sync/PocketSource.swift index a4029793f..8a5e8a0f6 100644 --- a/PocketKit/Sources/Sync/PocketSource.swift +++ b/PocketKit/Sources/Sync/PocketSource.swift @@ -208,31 +208,42 @@ extension PocketSource { } public func favorite(item: SavedItem) { + guard let remoteID = item.remoteID else { + return + } + item.isFavorite = true try? space.save() let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: FavoriteItemMutation(itemID: item.remoteID) + mutation: FavoriteItemMutation(itemID: remoteID) ) - enqueue(operation: operation, task: .favorite(remoteID: item.remoteID)) + enqueue(operation: operation, task: .favorite(remoteID: remoteID)) } public func unfavorite(item: SavedItem) { + guard let remoteID = item.remoteID else { + return + } + item.isFavorite = false try? space.save() let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: UnfavoriteItemMutation(itemID: item.remoteID) + mutation: UnfavoriteItemMutation(itemID: remoteID) ) - enqueue(operation: operation, task: .unfavorite(remoteID: item.remoteID)) + enqueue(operation: operation, task: .unfavorite(remoteID: remoteID)) } public func delete(item savedItem: SavedItem) { + guard let remoteID = savedItem.remoteID else { + return + } let item = savedItem.item @@ -247,13 +258,17 @@ extension PocketSource { let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: DeleteItemMutation(itemID: savedItem.remoteID) + mutation: DeleteItemMutation(itemID: remoteID) ) - enqueue(operation: operation, task: .delete(remoteID: savedItem.remoteID)) + enqueue(operation: operation, task: .delete(remoteID: remoteID)) } public func archive(item: SavedItem) { + guard let remoteID = item.remoteID else { + return + } + item.isArchived = true item.archivedAt = Date() try? space.save() @@ -261,10 +276,10 @@ extension PocketSource { let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: ArchiveItemMutation(itemID: item.remoteID) + mutation: ArchiveItemMutation(itemID: remoteID) ) - enqueue(operation: operation, task: .archive(remoteID: item.remoteID)) + enqueue(operation: operation, task: .archive(remoteID: remoteID)) } public func unarchive(item: SavedItem) { @@ -295,6 +310,10 @@ extension PocketSource { } public func addTags(item: SavedItem, tags: [String]) { + guard let remoteID = item.remoteID else { + return + } + item.tags = NSOrderedSet(array: tags.compactMap { $0 }.map({ tag in space.fetchOrCreateTag(byName: tag) })) @@ -304,10 +323,10 @@ extension PocketSource { let operation = operations.savedItemMutationOperation( apollo: apollo, events: _events, - mutation: getMutation(for: tags, and: item.remoteID) + mutation: getMutation(for: tags, and: remoteID) ) - enqueue(operation: operation, task: .addTags(remoteID: item.remoteID, tags: tags)) + enqueue(operation: operation, task: .addTags(remoteID: remoteID, tags: tags)) } public func deleteTag(tag: Tag) { @@ -354,8 +373,12 @@ extension PocketSource { } public func fetchDetails(for savedItem: SavedItem) async throws { + guard let remoteID = savedItem.remoteID else { + return + } + guard let remoteSavedItem = try await apollo - .fetch(query: SavedItemByIDQuery(id: savedItem.remoteID)) + .fetch(query: SavedItemByIDQuery(id: remoteID)) .data?.user?.savedItemById else { return } @@ -412,7 +435,7 @@ extension PocketSource { // MARK: - Enqueueing and Restoring offline operations extension PocketSource { private func enqueue(operation: SyncOperation, task: SyncTask, completion: (() -> Void)? = nil) { - let persistentTask: PersistentSyncTask = space.new() + let persistentTask: PersistentSyncTask = PersistentSyncTask(context: space.context) persistentTask.createdAt = Date() persistentTask.syncTaskContainer = SyncTaskContainer(task: task) try? space.save() @@ -568,7 +591,7 @@ extension PocketSource { if let savedItem = recommendation.item?.savedItem { unarchive(item: savedItem) } else { - let savedItem: SavedItem = space.new() + let savedItem: SavedItem = SavedItem(context: space.context, url: item.givenURL) savedItem.update(from: recommendation) try? space.save() @@ -607,7 +630,7 @@ extension PocketSource { if let savedItem = try? space.fetchSavedItem(byURL: url) { unarchive(item: savedItem) } else { - let savedItem: SavedItem = space.new() + let savedItem: SavedItem = SavedItem(context: space.context, url: url) savedItem.url = url savedItem.createdAt = Date() try? space.save() @@ -624,14 +647,15 @@ extension PocketSource { } public func fetchOrCreateSavedItem(with remoteID: String, and remoteParts: SavedItem.RemoteSavedItem?) -> SavedItem? { - var savedItem: SavedItem? = try? space.fetchSavedItem(byRemoteID: remoteID) - guard savedItem == nil else { return savedItem } - savedItem = space.new() + guard let remoteParts, let url = URL(string: remoteParts.url) else { + //TODO: Daniel log error + return nil + } + + let savedItem = (try? space.fetchSavedItem(byRemoteID: remoteID)) ?? SavedItem(context: space.context, url: url, remoteID: remoteID) + savedItem.update(from: remoteParts, with: space) try? space.save() - if let remoteParts = remoteParts { - savedItem?.update(from: remoteParts, with: space) - } return savedItem } } diff --git a/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift b/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift index 3f772aa11..3d14dc1a6 100644 --- a/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift +++ b/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift @@ -11,7 +11,7 @@ extension SlateLineup { experimentID = remote.experimentId slates = try? NSOrderedSet(array: remote.slates.map { remoteSlate in - let slate = try space.fetchSlate(byRemoteID: remoteSlate.id) ?? space.new() + let slate = try space.fetchSlate(byRemoteID: remoteSlate.id) ?? Slate(context: space.context, remoteID: remoteSlate.id, expermimentID: remoteSlate.experimentId, requestID: remoteSlate.requestId) slate.update(from: remoteSlate.fragments.slateParts, in: space) return slate @@ -31,7 +31,7 @@ extension Slate { recommendations = NSOrderedSet(array: remote.recommendations.compactMap { remote in guard let remoteID = remote.id, - let recommendation = try? space.fetchOrCreateRecommendation(byRemoteID: remoteID) else { + let recommendation = try? space.fetchRecommendation(byRemoteID: remoteID) ?? Recommendation(context: space.context, remoteID: remoteID) else { return nil } recommendation.update(from: remote, in: space) @@ -44,7 +44,7 @@ extension Recommendation { public typealias RemoteRecommendation = SlateParts.Recommendation func update(from remote: RemoteRecommendation, in space: Space) { - guard let id = remote.id else { + guard let id = remote.id, let url = URL(string: remote.item.givenUrl) else { //TODO: Daniel log, also daniel work to make this non-null in the API. return } @@ -54,8 +54,8 @@ extension Recommendation { excerpt = remote.curatedInfo?.excerpt imageURL = remote.curatedInfo?.imageSrc.flatMap(URL.init) - let recommendationItem = try? space.fetchOrCreateItem(byRemoteID: remote.item.remoteID) - recommendationItem?.update(from: remote.item.fragments.itemSummary) + let recommendationItem = (try? space.fetchItem(byRemoteID: remote.item.remoteID)) ?? Item(context: space.context, givenURL: url, remoteID: remoteID) + recommendationItem.update(from: remote.item.fragments.itemSummary) item = recommendationItem } } diff --git a/PocketKit/Sources/Sync/Slates/SlateService.swift b/PocketKit/Sources/Sync/Slates/SlateService.swift index 852f867d7..4a169dd57 100644 --- a/PocketKit/Sources/Sync/Slates/SlateService.swift +++ b/PocketKit/Sources/Sync/Slates/SlateService.swift @@ -43,7 +43,7 @@ class APISlateService: SlateService { @MainActor private func handle(remote: GetSlateLineupQuery.Data.GetSlateLineup) throws { - let lineup = try space.fetchSlateLineup(byRemoteID: remote.id) ?? space.new() + let lineup = (try? space.fetchSlateLineup(byRemoteID: remote.id)) ?? SlateLineup(context: space.context, remoteID: remote.id, expermimentID: remote.experimentId, requestID: remote.requestId) lineup.update(from: remote, in: space) try space.save() @@ -53,7 +53,7 @@ class APISlateService: SlateService { @MainActor private func handle(remote: SlateParts) throws { - let slate = try space.fetchOrCreateSlate(byRemoteID: remote.id) + let slate = (try? space.fetchSlate(byRemoteID: remote.id)) ?? Slate(context: space.context, remoteID: remote.id, expermimentID: remote.experimentId, requestID: remote.requestId) slate.update(from: remote, in: space) try space.save() diff --git a/PocketKit/Sources/Sync/Space.swift b/PocketKit/Sources/Sync/Space.swift index f4cd2dba2..c6528c5c8 100644 --- a/PocketKit/Sources/Sync/Space.swift +++ b/PocketKit/Sources/Sync/Space.swift @@ -50,10 +50,6 @@ public class Space { return try fetch(Requests.fetchAllSavedItems()) } - func fetchOrCreateSavedItem(byRemoteID itemID: String) throws -> SavedItem { - try fetchSavedItem(byRemoteID: itemID) ?? new() - } - func fetchPersistentSyncTasks() throws -> [PersistentSyncTask] { return try fetch(Requests.fetchPersistentSyncTasks()) } @@ -74,10 +70,6 @@ public class Space { return try fetch(Requests.fetchSlateLineup(byID: id)).first } - func fetchOrCreateSlateLineup(byRemoteID id: String) throws -> SlateLineup { - try fetchSlateLineup(byRemoteID: id) ?? new() - } - func fetchSlates() throws -> [Slate] { return try fetch(Requests.fetchSlates()) } @@ -89,10 +81,6 @@ public class Space { return try fetch(request).first } - func fetchOrCreateSlate(byRemoteID id: String) throws -> Slate { - return try fetchSlate(byRemoteID: id) ?? new() - } - func fetchRecommendations() throws -> [Recommendation] { return try fetch(Requests.fetchRecommendations()) } @@ -104,10 +92,6 @@ public class Space { return try fetch(request).first } - func fetchOrCreateRecommendation(byRemoteID id: String) throws -> Recommendation { - return try fetchRecommendation(byRemoteID: id) ?? new() - } - func fetchItems() throws -> [Item] { return try fetch(Requests.fetchItems()) } @@ -156,10 +140,6 @@ public class Space { delete(tag) } - func fetchOrCreateItem(byRemoteID id: String) throws -> Item { - return try fetchItem(byRemoteID: id) ?? new() - } - func fetchUnsavedItems() throws -> [Item] { return try fetch(Requests.fetchUnsavedItems()) } @@ -168,10 +148,6 @@ public class Space { try context.fetch(request) } - func new() -> T { - return T(context: context) - } - func delete(_ object: NSManagedObject) { context.delete(object) } diff --git a/PocketKit/Tests/PocketKitTests/ArchivedItemsListViewModelTests.swift b/PocketKit/Tests/PocketKitTests/ArchivedItemsListViewModelTests.swift index 93eb55b4d..970079fef 100644 --- a/PocketKit/Tests/PocketKitTests/ArchivedItemsListViewModelTests.swift +++ b/PocketKit/Tests/PocketKitTests/ArchivedItemsListViewModelTests.swift @@ -283,7 +283,7 @@ class ArchivedItemsListViewModelTests: XCTestCase { } func test_shouldSelectCell_whenItemIsPending_returnsFalse() { - let items = [space.buildPendingSavedItem(), space.buildSavedItem()] + let items = [space.buildSavedItem(), space.buildSavedItem()] archiveService._results = items.map { .loaded($0) } let viewModel = subject() diff --git a/PocketKit/Tests/PocketKitTests/PocketAddTagsViewModelTests.swift b/PocketKit/Tests/PocketKitTests/PocketAddTagsViewModelTests.swift index e70860d66..8fc5605ca 100644 --- a/PocketKit/Tests/PocketKitTests/PocketAddTagsViewModelTests.swift +++ b/PocketKit/Tests/PocketKitTests/PocketAddTagsViewModelTests.swift @@ -74,10 +74,9 @@ class PocketAddTagsViewModelTests: XCTestCase { let expectRetrieveTagsCall = expectation(description: "expect source.retrieveTags(excluding:)") source.stubRetrieveTags { [weak self] _ in - guard let self = self else { return nil } defer { expectRetrieveTagsCall.fulfill() } - let tag2: Tag = self.space.new() - let tag3: Tag = self.space.new() + let tag2: Tag = Tag(context: self!.space.context) + let tag3: Tag = Tag(context: self!.space.context) tag2.name = "tag 2" tag3.name = "tag 3" return [tag2, tag3] diff --git a/PocketKit/Tests/PocketKitTests/RecommendationViewModelTests.swift b/PocketKit/Tests/PocketKitTests/RecommendationViewModelTests.swift index 7f57b2be5..19e2d61f2 100644 --- a/PocketKit/Tests/PocketKitTests/RecommendationViewModelTests.swift +++ b/PocketKit/Tests/PocketKitTests/RecommendationViewModelTests.swift @@ -380,7 +380,7 @@ class RecommendationViewModelTests: XCTestCase { return recommendation.item } - let webViewActivityList = viewModel.webViewActivityItems(url: recommendation.item!.givenURL!) + let webViewActivityList = viewModel.webViewActivityItems(url: recommendation.item!.givenURL) XCTAssertEqual(webViewActivityList[0].activityTitle, "Save") XCTAssertEqual(webViewActivityList[1].activityTitle, "Report") @@ -402,7 +402,7 @@ class RecommendationViewModelTests: XCTestCase { return recommendation.item } - let webViewActivityList = viewModel.webViewActivityItems(url: recommendation.item!.givenURL!) + let webViewActivityList = viewModel.webViewActivityItems(url: recommendation.item!.givenURL) XCTAssertEqual(webViewActivityList[0].activityTitle, "Archive") XCTAssertEqual(webViewActivityList[1].activityTitle, "Delete") XCTAssertEqual(webViewActivityList[2].activityTitle, "Favorite") diff --git a/PocketKit/Tests/PocketKitTests/SavedItemViewModelTests.swift b/PocketKit/Tests/PocketKitTests/SavedItemViewModelTests.swift index 7966cec37..a76180de8 100644 --- a/PocketKit/Tests/PocketKitTests/SavedItemViewModelTests.swift +++ b/PocketKit/Tests/PocketKitTests/SavedItemViewModelTests.swift @@ -264,7 +264,7 @@ class SavedItemViewModelTests: XCTestCase { return savedItem.item } - let webViewActivityList = viewModel.webViewActivityItems(url: savedItem.url!) + let webViewActivityList = viewModel.webViewActivityItems(url: savedItem.url) XCTAssertEqual(webViewActivityList[0].activityTitle, "Archive") XCTAssertEqual(webViewActivityList[1].activityTitle, "Delete") XCTAssertEqual(webViewActivityList[2].activityTitle, "Favorite") @@ -284,7 +284,7 @@ class SavedItemViewModelTests: XCTestCase { return savedItem.item } - let webViewActivityList = viewModel.webViewActivityItems(url: savedItem.url!) + let webViewActivityList = viewModel.webViewActivityItems(url: savedItem.url) XCTAssertEqual(webViewActivityList[0].activityTitle, "Move to Saves") XCTAssertEqual(webViewActivityList[1].activityTitle, "Delete") XCTAssertEqual(webViewActivityList[2].activityTitle, "Favorite") diff --git a/PocketKit/Tests/PocketKitTests/SavedItemsListViewModelTests.swift b/PocketKit/Tests/PocketKitTests/SavedItemsListViewModelTests.swift index 3391d621e..1710d96ad 100644 --- a/PocketKit/Tests/PocketKitTests/SavedItemsListViewModelTests.swift +++ b/PocketKit/Tests/PocketKitTests/SavedItemsListViewModelTests.swift @@ -78,7 +78,7 @@ class SavedItemsListViewModelTests: XCTestCase { func test_shouldSelectCell_whenItemIsPending_returnsFalse() { let viewModel = subject() - let item = space.buildPendingSavedItem() + let item = space.buildSavedItem() source.stubObject { _ in item @@ -99,7 +99,7 @@ class SavedItemsListViewModelTests: XCTestCase { func test_selectCell_whenItemIsArticle_setsSelectedItemToReaderView() { let viewModel = subject() - let item = space.buildPendingSavedItem() + let item = space.buildSavedItem() source.stubObject { _ in item } viewModel.selectCell(with: .item(item.objectID), sender: UIView()) diff --git a/PocketKit/Tests/PocketKitTests/Support/Space+factories.swift b/PocketKit/Tests/PocketKitTests/Support/Space+factories.swift index ec51228d7..6fdf1b414 100644 --- a/PocketKit/Tests/PocketKitTests/Support/Space+factories.swift +++ b/PocketKit/Tests/PocketKitTests/Support/Space+factories.swift @@ -46,9 +46,9 @@ extension Space { item: Item? = nil ) -> SavedItem { context.performAndWait { - let savedItem: SavedItem = new() + let savedItem: SavedItem = SavedItem(context: context, url: URL(string: url)!) let tags: [Tag]? = tags?.map { tag -> Tag in - let newTag: Tag = new() + let newTag: Tag = Tag(context: context) newTag.name = tag return newTag } @@ -60,19 +60,11 @@ extension Space { savedItem.url = URL(string: url)! savedItem.cursor = cursor savedItem.tags = NSOrderedSet(array: tags ?? []) - savedItem.item = item ?? new() + savedItem.item = item ?? Item(context: context, givenURL: URL(string: url)!, remoteID: remoteID) return savedItem } } - - @discardableResult - func buildPendingSavedItem() -> SavedItem { - context.performAndWait { - let savedItem: SavedItem = new() - return savedItem - } - } } // MARK: - Item @@ -112,10 +104,9 @@ extension Space { syndicatedArticle: SyndicatedArticle? = nil ) -> Item { context.performAndWait { - let item: Item = new() + let item: Item = Item(context: context, givenURL: givenURL!, remoteID: remoteID) item.remoteID = remoteID item.title = title - item.givenURL = givenURL item.resolvedURL = resolvedURL item.isArticle = isArticle item.article = article @@ -157,10 +148,7 @@ extension Space { slates: [Slate] = [] ) -> SlateLineup { context.performAndWait { - let lineup: SlateLineup = new() - lineup.remoteID = remoteID - lineup.requestID = requestID - lineup.experimentID = experimentID + let lineup: SlateLineup = SlateLineup(context: context, remoteID: remoteID, expermimentID: experimentID, requestID: requestID) lineup.slates = NSOrderedSet(array: slates) return lineup @@ -204,11 +192,8 @@ extension Space { recommendations: [Recommendation] = [] ) -> Slate { context.performAndWait { - let slate: Slate = new() - slate.experimentID = experimentID - slate.remoteID = remoteID + let slate: Slate = Slate(context: context, remoteID: remoteID, expermimentID: experimentID, requestID: requestID) slate.name = name - slate.requestID = requestID slate.slateDescription = slateDescription slate.recommendations = NSOrderedSet(array: recommendations) @@ -244,8 +229,7 @@ extension Space { excerpt: String? = nil ) -> Recommendation { context.performAndWait { - let recommendation: Recommendation = new() - recommendation.remoteID = remoteID + let recommendation: Recommendation = Recommendation(context: context, remoteID: remoteID) recommendation.item = item recommendation.title = title recommendation.excerpt = excerpt @@ -266,7 +250,7 @@ extension Space { publisherName: String? = nil ) -> SyndicatedArticle { context.performAndWait { - let syndicatedArticle: SyndicatedArticle = new() + let syndicatedArticle: SyndicatedArticle = SyndicatedArticle(context: context) syndicatedArticle.title = title syndicatedArticle.imageURL = imageURL syndicatedArticle.excerpt = excerpt @@ -284,7 +268,7 @@ extension Space { isDownloaded: Bool = false ) -> Image { return context.performAndWait { - let image: Image = new() + let image: Image = Image(context: context) image.source = source image.isDownloaded = isDownloaded diff --git a/PocketKit/Tests/SaveToPocketKitTests/SaveToAddTagsViewModelTests.swift b/PocketKit/Tests/SaveToPocketKitTests/SaveToAddTagsViewModelTests.swift index f9f74e1c6..ef1edf854 100644 --- a/PocketKit/Tests/SaveToPocketKitTests/SaveToAddTagsViewModelTests.swift +++ b/PocketKit/Tests/SaveToPocketKitTests/SaveToAddTagsViewModelTests.swift @@ -67,7 +67,7 @@ class SaveToAddTagsViewModelTests: XCTestCase { XCTAssert(true, "expect call to save action") var tags: [Tag] = [] for index in 1...2 { - let tag: Tag = self.space.new() + let tag: Tag = Tag(context: self.space.context) tag.name = "tag \(index)" tags.append(tag) } @@ -85,7 +85,7 @@ class SaveToAddTagsViewModelTests: XCTestCase { retrieveAction: { _ in var tags: [Tag] = [] for index in 2...3 { - let tag: Tag = self.space.new() + let tag: Tag = Tag(context: self.space.context) tag.name = "tag \(index)" tags.append(tag) } diff --git a/PocketKit/Tests/SaveToPocketKitTests/SavedItemViewModelTests.swift b/PocketKit/Tests/SaveToPocketKitTests/SavedItemViewModelTests.swift index 1c4326990..d74cf2f23 100644 --- a/PocketKit/Tests/SaveToPocketKitTests/SavedItemViewModelTests.swift +++ b/PocketKit/Tests/SaveToPocketKitTests/SavedItemViewModelTests.swift @@ -39,7 +39,7 @@ class SavedItemViewModelTests: XCTestCase { consumerKey = "test-key" space = .testSpace() - let savedItem = SavedItem() + let savedItem = SavedItem(context: space.context, url: URL(string: "http://mozilla.com")!) saveService.stubSave { _ in .newItem(savedItem) } } @@ -183,7 +183,7 @@ extension SavedItemViewModelTests { accessToken: "mock-access-token", userIdentifier: "mock-user-identifier" ) - let savedItem = SavedItem() + let savedItem = SavedItem(context: self.space.context, url: URL(string: "http://mozilla.com")!) saveService.stubSave { _ in .existingItem(savedItem) } let provider = MockItemProvider() @@ -288,7 +288,7 @@ extension SavedItemViewModelTests { func test_retrieveTags_updatesInfoViewModel() async { let viewModel = subject() saveService.stubRetrieveTags { _ in - let tag: Tag = self.space.new() + let tag: Tag = Tag(context: self.space.context) tag.name = "tag 1" return [tag] } diff --git a/PocketKit/Tests/SaveToPocketKitTests/Support/Space+factories.swift b/PocketKit/Tests/SaveToPocketKitTests/Support/Space+factories.swift index 1722717e7..949a7bcca 100644 --- a/PocketKit/Tests/SaveToPocketKitTests/Support/Space+factories.swift +++ b/PocketKit/Tests/SaveToPocketKitTests/Support/Space+factories.swift @@ -46,9 +46,9 @@ extension Space { item: Item? = nil ) -> SavedItem { context.performAndWait { - let savedItem: SavedItem = new() + let savedItem: SavedItem = SavedItem(context: context, url: URL(string: url)!, remoteID: remoteID) let tags: [Tag]? = tags?.map { tag -> Tag in - let newTag: Tag = new() + let newTag: Tag = Tag(context: context) newTag.name = tag return newTag } @@ -60,19 +60,11 @@ extension Space { savedItem.url = URL(string: url)! savedItem.cursor = cursor savedItem.tags = NSOrderedSet(array: tags ?? []) - savedItem.item = item ?? new() + savedItem.item = item ?? Item(context: context, givenURL: URL(string: url)!, remoteID: remoteID) return savedItem } } - - @discardableResult - func buildPendingSavedItem() -> SavedItem { - context.performAndWait { - let savedItem: SavedItem = new() - return savedItem - } - } } // MARK: - Item @@ -109,10 +101,9 @@ extension Space { article: Article? = nil ) -> Item { context.performAndWait { - let item: Item = new() + let item: Item = Item(context: context, givenURL: givenURL!, remoteID: remoteID) item.remoteID = remoteID item.title = title - item.givenURL = givenURL item.resolvedURL = resolvedURL item.isArticle = isArticle item.article = article @@ -152,10 +143,7 @@ extension Space { slates: [Slate] = [] ) -> SlateLineup { context.performAndWait { - let lineup: SlateLineup = new() - lineup.remoteID = remoteID - lineup.requestID = requestID - lineup.experimentID = experimentID + let lineup: SlateLineup = SlateLineup(context: context, remoteID: remoteID, expermimentID: experimentID, requestID: requestID) lineup.slates = NSOrderedSet(array: slates) return lineup @@ -199,11 +187,8 @@ extension Space { recommendations: [Recommendation] = [] ) -> Slate { context.performAndWait { - let slate: Slate = new() - slate.experimentID = experimentID - slate.remoteID = remoteID + let slate: Slate = Slate(context: context, remoteID: remoteID, expermimentID: experimentID, requestID: requestID) slate.name = name - slate.requestID = requestID slate.slateDescription = slateDescription slate.recommendations = NSOrderedSet(array: recommendations) @@ -236,8 +221,7 @@ extension Space { item: Item? = nil ) -> Recommendation { context.performAndWait { - let recommendation: Recommendation = new() - recommendation.remoteID = remoteID + let recommendation: Recommendation = Recommendation(context: context, remoteID: remoteID) recommendation.item = item return recommendation @@ -253,7 +237,7 @@ extension Space { isDownloaded: Bool = false ) -> Image { return context.performAndWait { - let image: Image = new() + let image: Image = Image(context: context) image.source = source image.isDownloaded = isDownloaded diff --git a/PocketKit/Tests/SyncTests/APISlateServiceTests.swift b/PocketKit/Tests/SyncTests/APISlateServiceTests.swift index 2a6e75c32..701703da0 100644 --- a/PocketKit/Tests/SyncTests/APISlateServiceTests.swift +++ b/PocketKit/Tests/SyncTests/APISlateServiceTests.swift @@ -81,7 +81,7 @@ extension APISlateServiceTests { let item = recommendation.item XCTAssertNotNil(item) XCTAssertEqual(item!.remoteID, "item-1") - XCTAssertEqual(item!.givenURL?.absoluteString, "https://given.example.com/rec-1") + XCTAssertEqual(item!.givenURL.absoluteString, "https://given.example.com/rec-1") XCTAssertEqual(item!.resolvedURL?.absoluteString, "https://resolved.example.com/rec-1") XCTAssertEqual(item!.title, "Slate 1, Recommendation 1") XCTAssertEqual(item!.language, "en") @@ -144,20 +144,20 @@ extension APISlateServiceTests { // 2. Old slates should be deleted let fetchedSlates = try space.fetchSlates() - let fetchedSlateIDs = fetchedSlates.map { $0.remoteID! } + let fetchedSlateIDs = fetchedSlates.map { $0.remoteID } XCTAssertEqual(fetchedSlates.count, 2) XCTAssertEqual(Set(fetchedSlateIDs), ["slate-1", "slate-2"]) // 3. Old recommendations should be deleted let fetchedRecommendations = try space.fetchRecommendations() XCTAssertEqual(fetchedRecommendations.count, 3) - let fetchedRecommendationIDs = fetchedRecommendations.map { $0.remoteID! } + let fetchedRecommendationIDs = fetchedRecommendations.map { $0.remoteID } XCTAssertFalse(fetchedRecommendationIDs.contains("slate-1-recommendation-1-seed")) // 4. Old items should be removed let items = try space.fetchItems() XCTAssertEqual(items.count, 3) - let itemIDs = items.map { $0.remoteID! } + let itemIDs = items.map { $0.remoteID } XCTAssertEqual(Set(itemIDs), ["item-1", "item-2", "item-3"]) } @@ -237,7 +237,7 @@ extension APISlateServiceTests { let item = recommendation.item XCTAssertNotNil(item) XCTAssertEqual(item!.remoteID, "item-1") - XCTAssertEqual(item!.givenURL?.absoluteString, "https://given.example.com/rec-1") + XCTAssertEqual(item!.givenURL.absoluteString, "https://given.example.com/rec-1") XCTAssertEqual(item!.resolvedURL?.absoluteString, "https://resolved.example.com/rec-1") XCTAssertEqual(item!.title, "Slate 1, Recommendation 1") XCTAssertEqual(item!.language, "en") diff --git a/PocketKit/Tests/SyncTests/Operations/SaveItemOperationTests.swift b/PocketKit/Tests/SyncTests/Operations/SaveItemOperationTests.swift index d8eb9299f..89c9e4db3 100644 --- a/PocketKit/Tests/SyncTests/Operations/SaveItemOperationTests.swift +++ b/PocketKit/Tests/SyncTests/Operations/SaveItemOperationTests.swift @@ -79,7 +79,7 @@ class SaveItemOperationTests: XCTestCase { toReturnError: TestError.anError ) - let service = subject(managedItemID: savedItem.objectID, url: savedItem.url!) + let service = subject(managedItemID: savedItem.objectID, url: savedItem.url) let result = await service.execute() guard case .failure = result else { @@ -93,7 +93,7 @@ class SaveItemOperationTests: XCTestCase { apollo.stubPerform(ofMutationType: SaveItemMutation.self, toReturnError: initialError) let savedItem = try space.createSavedItem() - let service = subject(managedItemID: savedItem.objectID, url: savedItem.url!) + let service = subject(managedItemID: savedItem.objectID, url: savedItem.url) let result = await service.execute() guard case .retry = result else { @@ -112,7 +112,7 @@ class SaveItemOperationTests: XCTestCase { apollo.stubPerform(ofMutationType: SaveItemMutation.self, toReturnError: initialError) let savedItem = try space.createSavedItem() - let service = subject(managedItemID: savedItem.objectID, url: savedItem.url!) + let service = subject(managedItemID: savedItem.objectID, url: savedItem.url) let result = await service.execute() guard case .retry = result else { diff --git a/PocketKit/Tests/SyncTests/PocketSaveServiceTests.swift b/PocketKit/Tests/SyncTests/PocketSaveServiceTests.swift index ab5d7e636..ea58b0476 100644 --- a/PocketKit/Tests/SyncTests/PocketSaveServiceTests.swift +++ b/PocketKit/Tests/SyncTests/PocketSaveServiceTests.swift @@ -383,9 +383,9 @@ extension PocketSaveServiceTests { } func test_retrieveTags_updatesInfoViewModel() { - let tag: Tag = space.new() + let tag: Tag = Tag(context: space.context) tag.name = "tag 1" - let tag2: Tag = space.new() + let tag2: Tag = Tag(context: space.context) tag2.name = "tag 2" let service = subject() let tags = service.retrieveTags(excluding: ["tag 1"]) diff --git a/PocketKit/Tests/SyncTests/PocketSourceTests+savedItemEvents.swift b/PocketKit/Tests/SyncTests/PocketSourceTests+savedItemEvents.swift index 46eea2d87..a6c545135 100644 --- a/PocketKit/Tests/SyncTests/PocketSourceTests+savedItemEvents.swift +++ b/PocketKit/Tests/SyncTests/PocketSourceTests+savedItemEvents.swift @@ -34,7 +34,7 @@ extension PocketSourceTests { receivedNotification.fulfill() }.store(in: &subscriptions) - let notification: SavedItemUpdatedNotification = space.new() + let notification: SavedItemUpdatedNotification = SavedItemUpdatedNotification(context: space.context) notification.savedItem = savedItem try! space.save() @@ -57,7 +57,7 @@ extension PocketSourceTests { var source: PocketSource? = subject() let savedItem = try! space.createSavedItem() - let unresolved: UnresolvedSavedItem = space.new() + let unresolved: UnresolvedSavedItem = UnresolvedSavedItem(context: space.context) unresolved.savedItem = savedItem try space.save() @@ -78,11 +78,11 @@ extension PocketSourceTests { let source = subject() let savedItem = try! space.createSavedItem() - let notification1: SavedItemUpdatedNotification = space.new() + let notification1: SavedItemUpdatedNotification = SavedItemUpdatedNotification(context: space.context) notification1.savedItem = savedItem try! space.save() - let notification2: SavedItemUpdatedNotification = space.new() + let notification2: SavedItemUpdatedNotification = SavedItemUpdatedNotification(context: space.context) notification2.savedItem = savedItem try space.save() diff --git a/PocketKit/Tests/SyncTests/PocketSourceTests.swift b/PocketKit/Tests/SyncTests/PocketSourceTests.swift index fec0c4235..8d363a4b5 100644 --- a/PocketKit/Tests/SyncTests/PocketSourceTests.swift +++ b/PocketKit/Tests/SyncTests/PocketSourceTests.swift @@ -183,7 +183,7 @@ class PocketSourceTests: XCTestCase { let item = savedItem.item! item.recommendation = space.buildRecommendation() - let remoteItemID = item.remoteID! + let remoteItemID = item.remoteID let source = subject() source.delete(item: savedItem) @@ -200,7 +200,7 @@ class PocketSourceTests: XCTestCase { let savedItem = try space.createSavedItem(item: space.buildItem()) let item = savedItem.item! - let remoteItemID = item.remoteID! + let remoteItemID = item.remoteID let source = subject() source.delete(item: savedItem) @@ -295,7 +295,7 @@ class PocketSourceTests: XCTestCase { let source = subject() let savedItem = try! space.createSavedItem() - let unresolved: UnresolvedSavedItem = space.new() + let unresolved: UnresolvedSavedItem = UnresolvedSavedItem(context: space.context) unresolved.savedItem = savedItem try space.save() @@ -392,7 +392,7 @@ class PocketSourceTests: XCTestCase { } func test_downloadImage_updatesIsDownloadedProperty() throws { - let image: Image = space.new() + let image: Image = Image(context: space.context) let source = subject() source.download(images: [image]) @@ -596,7 +596,7 @@ extension PocketSourceTests { } func test_renameTag_executesUpdateTagMutation() throws { - let tag1: Tag = space.new() + let tag1: Tag = Tag(context: space.context) tag1.remoteID = "id 1" tag1.name = "tag 1" let source = subject() @@ -618,7 +618,7 @@ extension PocketSourceTests { private func createItemsWithTags(_ number: Int, isArchived: Bool = false) -> [SavedItem] { guard number > 0 else { return [] } return (1...number).compactMap { num in - let tag: Tag = space.new() + let tag: Tag = Tag(context: space.context) tag.remoteID = "id \(num)" tag.name = "tag \(num)" return space.buildSavedItem(isArchived: isArchived, tags: [tag]) diff --git a/PocketKit/Tests/SyncTests/SpaceTests.swift b/PocketKit/Tests/SyncTests/SpaceTests.swift index a281a88c4..a87ed8a6d 100644 --- a/PocketKit/Tests/SyncTests/SpaceTests.swift +++ b/PocketKit/Tests/SyncTests/SpaceTests.swift @@ -32,7 +32,7 @@ class SpaceTests: XCTestCase { func testFetchTagsForSavedAndArchivedItems() throws { let space = subject() - let tag: Tag = space.new() + let tag: Tag = Tag(context: space.context) tag.name = "tag 0" _ = space.buildSavedItem(tags: [tag]) _ = createItemsWithTags(2, isArchived: true) @@ -43,7 +43,7 @@ class SpaceTests: XCTestCase { func testFetchOrCreateTags() throws { let space = subject() - let tag1: Tag = space.new() + let tag1: Tag = Tag(context: space.context) tag1.name = "tag 1" let fetchedTag1 = space.fetchOrCreateTag(byName: "tag 1") @@ -56,7 +56,7 @@ class SpaceTests: XCTestCase { func testFetchTagsByID() throws { let space = subject() - let tag1: Tag = space.new() + let tag1: Tag = Tag(context: space.context) tag1.remoteID = "id 1" tag1.name = "tag 1" @@ -85,7 +85,7 @@ class SpaceTests: XCTestCase { guard number > 0 else { return [] } return (1...number).compactMap { num in let space = subject() - let tag: Tag = space.new() + let tag: Tag = Tag(context: space.context) tag.remoteID = "id \(num)" tag.name = "tag \(num)" return space.buildSavedItem(isArchived: isArchived, tags: [tag]) diff --git a/PocketKit/Tests/SyncTests/Support/Space+factories.swift b/PocketKit/Tests/SyncTests/Support/Space+factories.swift index fc685af22..50ff571ab 100644 --- a/PocketKit/Tests/SyncTests/Support/Space+factories.swift +++ b/PocketKit/Tests/SyncTests/Support/Space+factories.swift @@ -46,16 +46,15 @@ extension Space { item: Item? = nil ) -> SavedItem { context.performAndWait { - let savedItem: SavedItem = new() + let savedItem: SavedItem = SavedItem(context: context, url: URL(string: url)!) savedItem.remoteID = remoteID savedItem.isFavorite = isFavorite savedItem.isArchived = isArchived savedItem.createdAt = createdAt savedItem.archivedAt = archivedAt - savedItem.url = URL(string: url)! savedItem.cursor = cursor savedItem.tags = NSOrderedSet(array: tags ?? []) - savedItem.item = item ?? new() + savedItem.item = item ?? Item(context: context, givenURL: URL(string: url)!, remoteID: remoteID) return savedItem } @@ -68,7 +67,7 @@ extension Space { func createItem( remoteID: String = "item-1", title: String = "Item 1", - givenURL: URL? = URL(string: "https://example.com/items/item-1"), + givenURL: URL = URL(string: "https://example.com/items/item-1")!, isArticle: Bool = true ) throws -> Item { try context.performAndWait { @@ -88,15 +87,13 @@ extension Space { func buildItem( remoteID: String = "item-1", title: String = "Item 1", - givenURL: URL? = URL(string: "https://example.com/items/item-1"), + givenURL: URL? = URL(string: "https://example.com/items/item-1")!, resolvedURL: URL? = nil, isArticle: Bool = true ) -> Item { context.performAndWait { - let item: Item = new() - item.remoteID = remoteID + let item: Item = Item(context: context, givenURL: givenURL!, remoteID: remoteID) item.title = title - item.givenURL = givenURL item.resolvedURL = resolvedURL item.isArticle = isArticle @@ -135,10 +132,7 @@ extension Space { slates: [Slate] = [] ) -> SlateLineup { context.performAndWait { - let lineup: SlateLineup = new() - lineup.remoteID = remoteID - lineup.requestID = requestID - lineup.experimentID = experimentID + let lineup: SlateLineup = SlateLineup(context: context, remoteID: remoteID, expermimentID: experimentID, requestID: requestID) lineup.slates = NSOrderedSet(array: slates) return lineup @@ -182,11 +176,8 @@ extension Space { recommendations: [Recommendation] = [] ) -> Slate { context.performAndWait { - let slate: Slate = new() - slate.experimentID = experimentID - slate.remoteID = remoteID + let slate: Slate = Slate(context: context, remoteID: remoteID, expermimentID: experimentID, requestID: requestID) slate.name = name - slate.requestID = requestID slate.slateDescription = slateDescription slate.recommendations = NSOrderedSet(array: recommendations) @@ -219,8 +210,7 @@ extension Space { item: Item? = nil ) -> Recommendation { context.performAndWait { - let recommendation: Recommendation = new() - recommendation.remoteID = remoteID + let recommendation: Recommendation = Recommendation(context: context, remoteID: remoteID) recommendation.item = item return recommendation From 0c5958e06a3480eea67cc4ddc6d9093be68ad519 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Thu, 16 Feb 2023 15:22:30 -0800 Subject: [PATCH 5/6] fix(log): adding in logging statements --- .../CoreDataInitializers/Item+CoreDataClass.swift | 2 +- .../Recommendation+CoreDataClass.swift | 2 +- .../CoreDataInitializers/Slate+CoreDataClass.swift | 2 +- PocketKit/Sources/Sync/PocketSource.swift | 6 +++--- .../Sync/RemoteMapping/Item+remoteMapping.swift | 12 ++++++------ .../RemoteMapping/SavedItem+RemoteMapping.swift | 14 +++++++------- .../RemoteMapping/SlateLineup+remoteMapping.swift | 5 +++-- 7 files changed, 22 insertions(+), 21 deletions(-) diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataClass.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataClass.swift index 626c9c362..4049dc981 100644 --- a/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataClass.swift +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Item+CoreDataClass.swift @@ -20,7 +20,7 @@ public class Item: NSManagedObject { internal override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { super.init(entity: entity, insertInto: context) } - + public init(context: NSManagedObjectContext, givenURL: URL, remoteID: String) { diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataClass.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataClass.swift index 5c72ce638..046d5d5da 100644 --- a/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataClass.swift +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Recommendation+CoreDataClass.swift @@ -21,7 +21,7 @@ public class Recommendation: NSManagedObject { internal override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { super.init(entity: entity, insertInto: context) } - + public init( context: NSManagedObjectContext, remoteID: String diff --git a/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataClass.swift b/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataClass.swift index a87b39f02..bcf516c53 100644 --- a/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataClass.swift +++ b/PocketKit/Sources/Sync/CoreDataInitializers/Slate+CoreDataClass.swift @@ -16,7 +16,7 @@ public class Slate: NSManagedObject { public init(context: NSManagedObjectContext) { fatalError() } - + internal override init(entity: NSEntityDescription, insertInto context: NSManagedObjectContext?) { super.init(entity: entity, insertInto: context) } diff --git a/PocketKit/Sources/Sync/PocketSource.swift b/PocketKit/Sources/Sync/PocketSource.swift index 8a5e8a0f6..976658aec 100644 --- a/PocketKit/Sources/Sync/PocketSource.swift +++ b/PocketKit/Sources/Sync/PocketSource.swift @@ -376,7 +376,7 @@ extension PocketSource { guard let remoteID = savedItem.remoteID else { return } - + guard let remoteSavedItem = try await apollo .fetch(query: SavedItemByIDQuery(id: remoteID)) .data?.user?.savedItemById else { @@ -648,10 +648,10 @@ extension PocketSource { public func fetchOrCreateSavedItem(with remoteID: String, and remoteParts: SavedItem.RemoteSavedItem?) -> SavedItem? { guard let remoteParts, let url = URL(string: remoteParts.url) else { - //TODO: Daniel log error + Log.breadcrumb(category: "sync", level: .warning, message: "Skipping updating of SavedItem because we do not have a valid url or we have no remoteParts") return nil } - + let savedItem = (try? space.fetchSavedItem(byRemoteID: remoteID)) ?? SavedItem(context: space.context, url: url, remoteID: remoteID) savedItem.update(from: remoteParts, with: space) try? space.save() diff --git a/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift b/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift index c487a0924..f211cf69c 100644 --- a/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift +++ b/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift @@ -6,12 +6,12 @@ import PocketGraph extension Item { func update(remote: ItemParts) { remoteID = remote.remoteID - + guard let url = URL(string: remote.givenUrl) else { - //TODO: Daniel log an error. + Log.breadcrumb(category: "sync", level: .warning, message: "Skipping updating of Item \(remoteID) because \(givenURL) is not valid url") return } - + givenURL = url resolvedURL = remote.resolvedUrl.flatMap(URL.init) title = remote.title @@ -69,12 +69,12 @@ extension Item { func update(from summary: ItemSummary) { remoteID = summary.remoteID - + guard let url = URL(string: summary.givenUrl) else { - //TODO: Daniel log an error. + Log.breadcrumb(category: "sync", level: .warning, message: "Skipping updating of Item \(remoteID) because \(summary.givenUrl) is not valid url") return } - + givenURL = url resolvedURL = summary.resolvedUrl.flatMap(URL.init) title = summary.title diff --git a/PocketKit/Sources/Sync/RemoteMapping/SavedItem+RemoteMapping.swift b/PocketKit/Sources/Sync/RemoteMapping/SavedItem+RemoteMapping.swift index 59727d118..b09e84707 100644 --- a/PocketKit/Sources/Sync/RemoteMapping/SavedItem+RemoteMapping.swift +++ b/PocketKit/Sources/Sync/RemoteMapping/SavedItem+RemoteMapping.swift @@ -23,12 +23,12 @@ extension SavedItem { public func update(from remote: RemoteSavedItem, with space: Space) { remoteID = remote.remoteID - + guard let url = URL(string: remote.url) else { - //TODO: Daniel log an error. + Log.breadcrumb(category: "sync", level: .warning, message: "Skipping updating of SavedItem \(remoteID) because \(remote.url) is not valid url") return } - + self.url = url createdAt = Date(timeIntervalSince1970: TimeInterval(remote._createdAt)) deletedAt = remote._deletedAt.flatMap(TimeInterval.init).flatMap(Date.init(timeIntervalSince1970:)) @@ -62,10 +62,10 @@ extension SavedItem { public func update(from recommendation: Recommendation) { guard let url = recommendation.item?.bestURL else { - //TODO: Daniel log an error. + Log.breadcrumb(category: "sync", level: .warning, message: "Skipping updating of Recommendation \(recommendation.remoteID) from SavedItem \(self.remoteID) because \(recommendation.item?.bestURL) is not valid url") return } - + self.url = url self.createdAt = Date() @@ -75,10 +75,10 @@ extension SavedItem { public func update(from summary: SavedItemSummary, with space: Space) { remoteID = summary.remoteID guard let url = URL(string: summary.url) else { - //TODO: Daniel log an error. + Log.breadcrumb(category: "sync", level: .warning, message: "Skipping updating of SavedItem \(remoteID) because \(summary.url) is not valid url") return } - + self.url = url createdAt = Date(timeIntervalSince1970: TimeInterval(summary._createdAt)) deletedAt = summary._deletedAt.flatMap(TimeInterval.init).flatMap(Date.init(timeIntervalSince1970:)) diff --git a/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift b/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift index 3d14dc1a6..370e52827 100644 --- a/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift +++ b/PocketKit/Sources/Sync/RemoteMapping/SlateLineup+remoteMapping.swift @@ -45,10 +45,11 @@ extension Recommendation { func update(from remote: RemoteRecommendation, in space: Space) { guard let id = remote.id, let url = URL(string: remote.item.givenUrl) else { - //TODO: Daniel log, also daniel work to make this non-null in the API. + // TODO: Daniel work to make id non-null in the API Layer. + Log.breadcrumb(category: "sync", level: .warning, message: "Skipping updating of Recomendation because \(remote.item.givenUrl) is not valid url") return } - + remoteID = id title = remote.curatedInfo?.title excerpt = remote.curatedInfo?.excerpt From bc251161800dbeaab6439f7fa07e62aa835b7316 Mon Sep 17 00:00:00 2001 From: Daniel Brooks Date: Fri, 17 Feb 2023 09:59:32 -0800 Subject: [PATCH 6/6] fix(bug): fixing nil bug --- .../RemoteMapping/Item+remoteMapping.swift | 14 ++++++++-- .../ArchivedItemsListViewModelTests.swift | 2 +- .../SavedItemsListViewModelTests.swift | 4 +-- .../Support/Space+factories.swift | 26 ++++++++++++++++--- .../SyncTests/Support/Space+factories.swift | 11 +++++--- Tests iOS/AddTagsItemTests.swift | 2 +- Tests iOS/ReaderTests.swift | 2 +- 7 files changed, 47 insertions(+), 14 deletions(-) diff --git a/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift b/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift index f211cf69c..f3c3df61c 100644 --- a/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift +++ b/PocketKit/Sources/Sync/RemoteMapping/Item+remoteMapping.swift @@ -18,7 +18,13 @@ extension Item { topImageURL = remote.topImageUrl.flatMap(URL.init) domain = remote.domain language = remote.language - timeToRead = remote.timeToRead.flatMap(NSNumber.init) ?? 0 + + if let readTime = remote.timeToRead { + timeToRead = NSNumber(value: readTime) + } else { + timeToRead = 0 + } + excerpt = remote.excerpt datePublished = remote.datePublished.flatMap { DateFormatter.clientAPI.date(from: $0) } isArticle = remote.isArticle ?? false @@ -81,7 +87,11 @@ extension Item { topImageURL = summary.topImageUrl.flatMap(URL.init) domain = summary.domain language = summary.language - timeToRead = summary.timeToRead.flatMap(NSNumber.init) ?? 0 + if let readTime = summary.timeToRead { + timeToRead = NSNumber(value: readTime) + } else { + timeToRead = 0 + } excerpt = summary.excerpt datePublished = summary.datePublished.flatMap { DateFormatter.clientAPI.date(from: $0) } isArticle = summary.isArticle ?? false diff --git a/PocketKit/Tests/PocketKitTests/ArchivedItemsListViewModelTests.swift b/PocketKit/Tests/PocketKitTests/ArchivedItemsListViewModelTests.swift index 970079fef..93eb55b4d 100644 --- a/PocketKit/Tests/PocketKitTests/ArchivedItemsListViewModelTests.swift +++ b/PocketKit/Tests/PocketKitTests/ArchivedItemsListViewModelTests.swift @@ -283,7 +283,7 @@ class ArchivedItemsListViewModelTests: XCTestCase { } func test_shouldSelectCell_whenItemIsPending_returnsFalse() { - let items = [space.buildSavedItem(), space.buildSavedItem()] + let items = [space.buildPendingSavedItem(), space.buildSavedItem()] archiveService._results = items.map { .loaded($0) } let viewModel = subject() diff --git a/PocketKit/Tests/PocketKitTests/SavedItemsListViewModelTests.swift b/PocketKit/Tests/PocketKitTests/SavedItemsListViewModelTests.swift index 1710d96ad..3391d621e 100644 --- a/PocketKit/Tests/PocketKitTests/SavedItemsListViewModelTests.swift +++ b/PocketKit/Tests/PocketKitTests/SavedItemsListViewModelTests.swift @@ -78,7 +78,7 @@ class SavedItemsListViewModelTests: XCTestCase { func test_shouldSelectCell_whenItemIsPending_returnsFalse() { let viewModel = subject() - let item = space.buildSavedItem() + let item = space.buildPendingSavedItem() source.stubObject { _ in item @@ -99,7 +99,7 @@ class SavedItemsListViewModelTests: XCTestCase { func test_selectCell_whenItemIsArticle_setsSelectedItemToReaderView() { let viewModel = subject() - let item = space.buildSavedItem() + let item = space.buildPendingSavedItem() source.stubObject { _ in item } viewModel.selectCell(with: .item(item.objectID), sender: UIView()) diff --git a/PocketKit/Tests/PocketKitTests/Support/Space+factories.swift b/PocketKit/Tests/PocketKitTests/Support/Space+factories.swift index 6fdf1b414..8b724860c 100644 --- a/PocketKit/Tests/PocketKitTests/Support/Space+factories.swift +++ b/PocketKit/Tests/PocketKitTests/Support/Space+factories.swift @@ -65,6 +65,14 @@ extension Space { return savedItem } } + + @discardableResult + func buildPendingSavedItem() -> SavedItem { + context.performAndWait { + let savedItem: SavedItem = SavedItem(context: context, url: URL(string: "https://mozilla.com/example")!) + return savedItem + } + } } // MARK: - Item @@ -77,11 +85,16 @@ extension Space { isArticle: Bool = true, article: Article? = nil ) throws -> Item { - try context.performAndWait { + var url = givenURL + if url == nil { + url = URL(string: "https://example.com/items/item-1") + } + + return try context.performAndWait { let item = buildItem( remoteID: remoteID, title: title, - givenURL: givenURL, + givenURL: url, isArticle: isArticle, article: article ) @@ -103,8 +116,13 @@ extension Space { article: Article? = nil, syndicatedArticle: SyndicatedArticle? = nil ) -> Item { - context.performAndWait { - let item: Item = Item(context: context, givenURL: givenURL!, remoteID: remoteID) + var url = givenURL + if url == nil { + url = URL(string: "https://example.com/items/item-1") + } + + return context.performAndWait { + let item: Item = Item(context: context, givenURL: url!, remoteID: remoteID) item.remoteID = remoteID item.title = title item.resolvedURL = resolvedURL diff --git a/PocketKit/Tests/SyncTests/Support/Space+factories.swift b/PocketKit/Tests/SyncTests/Support/Space+factories.swift index 50ff571ab..d7d6b557e 100644 --- a/PocketKit/Tests/SyncTests/Support/Space+factories.swift +++ b/PocketKit/Tests/SyncTests/Support/Space+factories.swift @@ -87,12 +87,17 @@ extension Space { func buildItem( remoteID: String = "item-1", title: String = "Item 1", - givenURL: URL? = URL(string: "https://example.com/items/item-1")!, + givenURL: URL? = URL(string: "https://example.com/items/item-1"), resolvedURL: URL? = nil, isArticle: Bool = true ) -> Item { - context.performAndWait { - let item: Item = Item(context: context, givenURL: givenURL!, remoteID: remoteID) + var url = givenURL + if url == nil { + url = URL(string: "https://example.com/items/item-1") + } + + return context.performAndWait { + let item: Item = Item(context: context, givenURL: url!, remoteID: remoteID) item.title = title item.resolvedURL = resolvedURL item.isArticle = isArticle diff --git a/Tests iOS/AddTagsItemTests.swift b/Tests iOS/AddTagsItemTests.swift index 849218972..95e728d95 100644 --- a/Tests iOS/AddTagsItemTests.swift +++ b/Tests iOS/AddTagsItemTests.swift @@ -133,7 +133,7 @@ class AddTagsItemTests: XCTestCase { app.addTagsView.wait() app.addTagsView.allTagsView.wait() } - + func selectTaggedFilterButton() { app.saves.filterButton(for: "Tagged").tap() } diff --git a/Tests iOS/ReaderTests.swift b/Tests iOS/ReaderTests.swift index 2474ebef1..ce91e5b3e 100644 --- a/Tests iOS/ReaderTests.swift +++ b/Tests iOS/ReaderTests.swift @@ -43,7 +43,7 @@ class ReaderTests: XCTestCase { Fixture.data(name: "hello", ext: "html") } } - + try server.start() }