Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stub objects for Bookmarks sync #713

Merged
merged 10 commits into from
Mar 15, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,9 @@ let package = Package(
.copy("Resources/Bookmarks_V4.sqlite"),
.copy("Resources/Bookmarks_V4.sqlite-shm"),
.copy("Resources/Bookmarks_V4.sqlite-wal"),
.copy("Resources/Bookmarks_V5.sqlite"),
.copy("Resources/Bookmarks_V5.sqlite-shm"),
.copy("Resources/Bookmarks_V5.sqlite-wal"),
],
plugins: [swiftlintPlugin]
),
Expand Down
15 changes: 13 additions & 2 deletions Sources/Bookmarks/BookmarkEntity.swift
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ public class BookmarkEntity: NSManagedObject {
@NSManaged public var title: String?
@NSManaged public var url: String?
@NSManaged public var uuid: String?
@NSManaged public var isStub: Bool
@NSManaged public var children: NSOrderedSet?
@NSManaged public fileprivate(set) var lastChildrenPayloadReceivedFromSync: String?
@NSManaged public fileprivate(set) var favoriteFolders: NSSet?
Expand Down Expand Up @@ -127,6 +128,16 @@ public class BookmarkEntity: NSManagedObject {
try validate()
}

public override func prepareForDeletion() {
super.prepareForDeletion()

if isFolder {
for child in children?.array as? [BookmarkEntity] ?? [] where child.isStub {
managedObjectContext?.delete(child)
}
}
}

public var urlObject: URL? {
guard let url = url else { return nil }
return url.isBookmarklet() ? url.toEncodedBookmarklet() : URL(string: url)
Expand All @@ -138,12 +149,12 @@ public class BookmarkEntity: NSManagedObject {

public var childrenArray: [BookmarkEntity] {
let children = children?.array as? [BookmarkEntity] ?? []
return children.filter { $0.isPendingDeletion == false }
return children.filter { $0.isStub == false && $0.isPendingDeletion == false }
}

public var favoritesArray: [BookmarkEntity] {
let children = favorites?.array as? [BookmarkEntity] ?? []
return children.filter { $0.isPendingDeletion == false }
return children.filter { $0.isStub == false && $0.isPendingDeletion == false }
}

public var favoriteFoldersSet: Set<BookmarkEntity> {
Expand Down
5 changes: 3 additions & 2 deletions Sources/Bookmarks/BookmarkListViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,10 @@ public class BookmarkListViewModel: BookmarkListInteracting, ObservableObject {
public var totalBookmarksCount: Int {
let countRequest = BookmarkEntity.fetchRequest()
countRequest.predicate = NSPredicate(
format: "%K == false && %K == NO",
format: "%K == false && %K == NO && (%K == NO OR %K == nil)",
#keyPath(BookmarkEntity.isFolder),
#keyPath(BookmarkEntity.isPendingDeletion)
#keyPath(BookmarkEntity.isPendingDeletion),
#keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)
)

return (try? context.count(for: countRequest)) ?? 0
Expand Down
32 changes: 25 additions & 7 deletions Sources/Bookmarks/BookmarkUtils.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,10 +59,11 @@ public struct BookmarkUtils {
public static func fetchOrphanedEntities(_ context: NSManagedObjectContext) -> [BookmarkEntity] {
let request = BookmarkEntity.fetchRequest()
request.predicate = NSPredicate(
format: "NOT %K IN %@ AND %K == NO AND %K == nil",
format: "NOT %K IN %@ AND %K == NO AND (%K == NO OR %K == nil) AND %K == nil",
#keyPath(BookmarkEntity.uuid),
BookmarkEntity.Constants.favoriteFoldersIDs.union([BookmarkEntity.Constants.rootFolderID]),
#keyPath(BookmarkEntity.isPendingDeletion),
#keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub),
#keyPath(BookmarkEntity.parent)
)
request.sortDescriptors = [NSSortDescriptor(key: #keyPath(BookmarkEntity.uuid), ascending: true)]
Expand All @@ -71,6 +72,17 @@ public struct BookmarkUtils {
return (try? context.fetch(request)) ?? []
}

public static func fetchStubbedEntities(_ context: NSManagedObjectContext) -> [BookmarkEntity] {
let request = BookmarkEntity.fetchRequest()
request.predicate = NSPredicate(format: "%K == YES",
#keyPath(BookmarkEntity.isStub)
)
request.sortDescriptors = [NSSortDescriptor(key: #keyPath(BookmarkEntity.uuid), ascending: true)]
request.returnsObjectsAsFaults = false

return (try? context.fetch(request)) ?? []
}

public static func prepareFoldersStructure(in context: NSManagedObjectContext) {

if fetchRootFolder(context) == nil {
Expand Down Expand Up @@ -117,9 +129,10 @@ public struct BookmarkUtils {

public static func fetchAllBookmarksUUIDs(in context: NSManagedObjectContext) -> [String] {
let request = NSFetchRequest<NSFetchRequestResult>(entityName: "BookmarkEntity")
request.predicate = NSPredicate(format: "%K == NO AND %K == NO",
request.predicate = NSPredicate(format: "%K == NO AND %K == NO AND (%K == NO OR %K == nil)",
#keyPath(BookmarkEntity.isFolder),
#keyPath(BookmarkEntity.isPendingDeletion))
#keyPath(BookmarkEntity.isPendingDeletion),
#keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub))
request.resultType = .dictionaryResultType
request.propertiesToFetch = [#keyPath(BookmarkEntity.uuid)]

Expand All @@ -131,10 +144,11 @@ public struct BookmarkUtils {
predicate: NSPredicate = NSPredicate(value: true),
context: NSManagedObjectContext) -> BookmarkEntity? {
let request = BookmarkEntity.fetchRequest()
let urlPredicate = NSPredicate(format: "%K == %@ AND %K == NO",
let urlPredicate = NSPredicate(format: "%K == %@ AND %K == NO AND (%K == NO OR %K == nil)",
#keyPath(BookmarkEntity.url),
url.absoluteString,
#keyPath(BookmarkEntity.isPendingDeletion))
#keyPath(BookmarkEntity.isPendingDeletion),
#keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub))
request.predicate = NSCompoundPredicate(andPredicateWithSubpredicates: [urlPredicate, predicate])
request.returnsObjectsAsFaults = false
request.fetchLimit = 1
Expand All @@ -144,14 +158,18 @@ public struct BookmarkUtils {

public static func fetchBookmarksPendingDeletion(_ context: NSManagedObjectContext) -> [BookmarkEntity] {
let request = BookmarkEntity.fetchRequest()
request.predicate = NSPredicate(format: "%K == YES", #keyPath(BookmarkEntity.isPendingDeletion))
request.predicate = NSPredicate(format: "%K == YES AND (%K == NO OR %K == nil)",
#keyPath(BookmarkEntity.isPendingDeletion),
#keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub))

return (try? context.fetch(request)) ?? []
}

public static func fetchModifiedBookmarks(_ context: NSManagedObjectContext) -> [BookmarkEntity] {
let request = BookmarkEntity.fetchRequest()
request.predicate = NSPredicate(format: "%K != nil", #keyPath(BookmarkEntity.modifiedAt))
request.predicate = NSPredicate(format: "%K != nil AND (%K == NO OR %K == nil)",
#keyPath(BookmarkEntity.modifiedAt),
#keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub))

return (try? context.fetch(request)) ?? []
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,6 @@
<plist version="1.0">
<dict>
<key>_XCCurrentVersionName</key>
<string>BookmarksModel 5.xcdatamodel</string>
<string>BookmarksModel 6.xcdatamodel</string>
</dict>
</plist>
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<model type="com.apple.IDECoreDataModeler.DataModel" documentVersion="1.0" lastSavedToolsVersion="22522" systemVersion="22G313" minimumToolsVersion="Automatic" sourceLanguage="Swift" userDefinedModelVersionIdentifier="">
<entity name="BookmarkEntity" representedClassName="BookmarkEntity" syncable="YES">
<attribute name="isFolder" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="isPendingDeletion" optional="YES" attributeType="Boolean" defaultValueString="NO" usesScalarValueType="YES" versionHashModifier="3"/>
<attribute name="isStub" optional="YES" attributeType="Boolean" usesScalarValueType="YES"/>
<attribute name="lastChildrenPayloadReceivedFromSync" optional="YES" attributeType="String"/>
<attribute name="modifiedAt" optional="YES" attributeType="Date" usesScalarValueType="NO"/>
<attribute name="title" optional="YES" attributeType="String"/>
<attribute name="url" optional="YES" attributeType="String" versionHashModifier="3"/>
<attribute name="uuid" attributeType="String" versionHashModifier="3"/>
<relationship name="children" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="BookmarkEntity" inverseName="parent" inverseEntity="BookmarkEntity"/>
<relationship name="favoriteFolders" optional="YES" toMany="YES" deletionRule="Nullify" destinationEntity="BookmarkEntity" inverseName="favorites" inverseEntity="BookmarkEntity" elementID="favoriteFolder"/>
<relationship name="favorites" optional="YES" toMany="YES" deletionRule="Nullify" ordered="YES" destinationEntity="BookmarkEntity" inverseName="favoriteFolders" inverseEntity="BookmarkEntity"/>
<relationship name="parent" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="BookmarkEntity" inverseName="children" inverseEntity="BookmarkEntity"/>
<fetchIndex name="byUUID">
<fetchIndexElement property="uuid" type="Binary" order="ascending"/>
<fetchIndexElement property="isPendingDeletion" type="Binary" order="ascending"/>
</fetchIndex>
<fetchIndex name="byURL">
<fetchIndexElement property="url" type="Binary" order="ascending"/>
<fetchIndexElement property="isPendingDeletion" type="Binary" order="ascending"/>
</fetchIndex>
<fetchIndex name="byIsPendingDeletion">
<fetchIndexElement property="isPendingDeletion" type="Binary" order="ascending"/>
</fetchIndex>
</entity>
</model>
Original file line number Diff line number Diff line change
Expand Up @@ -229,10 +229,11 @@ final class FaviconsFetchOperation: Operation {
private func mapBookmarkDomainsToUUIDs(for uuids: any Sequence & CVarArg) -> BookmarkDomains {
let request = BookmarkEntity.fetchRequest()
request.predicate = NSPredicate(
format: "%K IN %@ AND %K == NO AND %K == NO",
format: "%K IN %@ AND %K == NO AND %K == NO AND (%K == NO OR %K == nil)",
#keyPath(BookmarkEntity.uuid), uuids,
#keyPath(BookmarkEntity.isFolder),
#keyPath(BookmarkEntity.isPendingDeletion)
#keyPath(BookmarkEntity.isPendingDeletion),
#keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)
)
request.propertiesToFetch = [#keyPath(BookmarkEntity.uuid), #keyPath(BookmarkEntity.url)]
request.relationshipKeyPathsForPrefetching = [#keyPath(BookmarkEntity.favoriteFolders), #keyPath(BookmarkEntity.parent)]
Expand Down
5 changes: 3 additions & 2 deletions Sources/Bookmarks/ImportExport/BookmarkCoreDataImporter.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,9 +60,10 @@ public class BookmarkCoreDataImporter {
private func bookmarkURLToID(in context: NSManagedObjectContext) throws -> [String: NSManagedObjectID] {
let fetch = NSFetchRequest<NSFetchRequestResult>(entityName: "BookmarkEntity")
fetch.predicate = NSPredicate(
format: "%K == false && %K == NO",
format: "%K == false && %K == NO AND (%K == NO OR %K == nil)",
#keyPath(BookmarkEntity.isFolder),
#keyPath(BookmarkEntity.isPendingDeletion)
#keyPath(BookmarkEntity.isPendingDeletion),
#keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)
)
fetch.resultType = .dictionaryResultType

Expand Down
5 changes: 3 additions & 2 deletions Sources/Bookmarks/MenuBookmarksViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,10 +136,11 @@ public class MenuBookmarksViewModel: MenuBookmarksInteracting {
}
return BookmarkUtils.fetchBookmark(for: url,
predicate: NSPredicate(
format: "ANY %K CONTAINS %@ AND %K == NO",
format: "ANY %K CONTAINS %@ AND %K == NO AND (%K == NO OR %K == nil)",
#keyPath(BookmarkEntity.favoriteFolders),
favoritesFolder,
#keyPath(BookmarkEntity.isPendingDeletion)
#keyPath(BookmarkEntity.isPendingDeletion),
#keyPath(BookmarkEntity.isStub), #keyPath(BookmarkEntity.isStub)
),
context: context)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ import Bookmarks
struct BookmarksTestDBBuilder {

static func main() {
generateDatabase(modelVersion: 3)
generateDatabase(modelVersion: 5)
}

private static func generateDatabase(modelVersion: Int) {
Expand Down
Loading
Loading