- [Fix]: migrations fix for coredata from v11(0.5.9) to v17_1(0.6.1) and v17(0.6.0 to v17_1(0.6.1)

This commit is contained in:
mahee96
2025-03-23 21:44:06 -07:00
parent a7496e08e3
commit a4dfd28a3c
20 changed files with 1962 additions and 1834 deletions

View File

@@ -35,31 +35,31 @@
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="permissions" inverseEntity="StoreApp"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="appBundleID"/>
<constraint value="sourceID"/>
<constraint value="appBundleID"/>
<constraint value="type"/>
<constraint value="permission"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="AppScreenshot" representedClassName="AppScreenshot" syncable="YES">
<attribute name="appBundleID" attributeType="String"/>
<attribute name="appBundleID" optional="YES" attributeType="String"/>
<attribute name="deviceType" attributeType="Integer 16" defaultValueString="0" usesScalarValueType="YES"/>
<attribute name="height" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
<attribute name="imageURL" attributeType="URI"/>
<attribute name="sourceID" attributeType="String"/>
<attribute name="sourceID" optional="YES" attributeType="String"/>
<attribute name="width" optional="YES" attributeType="Integer 16" usesScalarValueType="NO"/>
<relationship name="app" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="screenshots" inverseEntity="StoreApp"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="appBundleID"/>
<constraint value="sourceID"/>
<constraint value="appBundleID"/>
<constraint value="imageURL"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="AppVersion" representedClassName="AppVersion" syncable="YES">
<attribute name="appBundleID" attributeType="String"/>
<attribute name="appBundleID" optional="YES" attributeType="String"/>
<attribute name="buildVersion" optional="YES" attributeType="String"/>
<attribute name="channel" optional="YES" attributeType="String"/>
<attribute name="date" attributeType="Date" usesScalarValueType="NO"/>
@@ -76,8 +76,8 @@
<relationship name="releaseTrack" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="ReleaseTrack" inverseName="releases" inverseEntity="ReleaseTrack"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="appBundleID"/>
<constraint value="sourceID"/>
<constraint value="appBundleID"/>
<constraint value="version"/>
<constraint value="buildVersion"/>
</uniquenessConstraint>
@@ -220,9 +220,16 @@
<entity name="ReleaseTrack" representedClassName="ReleaseTrack" syncable="YES">
<attribute name="appBundleID" optional="YES" attributeType="String"/>
<attribute name="sourceID" optional="YES" attributeType="String"/>
<attribute name="track" optional="YES" attributeType="String"/>
<attribute name="track" attributeType="String"/>
<relationship name="releases" optional="YES" toMany="YES" deletionRule="Cascade" ordered="YES" destinationEntity="AppVersion" inverseName="releaseTrack" inverseEntity="AppVersion"/>
<relationship name="storeApp" optional="YES" maxCount="1" deletionRule="Nullify" destinationEntity="StoreApp" inverseName="releaseTracks" inverseEntity="StoreApp"/>
<uniquenessConstraints>
<uniquenessConstraint>
<constraint value="sourceID"/>
<constraint value="appBundleID"/>
<constraint value="track"/>
</uniquenessConstraint>
</uniquenessConstraints>
</entity>
<entity name="Source" representedClassName="Source" syncable="YES">
<attribute name="error" optional="YES" attributeType="Transformable" valueTransformerName="ALTSecureValueTransformer"/>

View File

@@ -213,10 +213,10 @@ extension MergePolicy{
}
default:
break
// break
// Unknown context-level conflict.
// assertionFailure("MergePolicy is only intended to work with database-level conflicts.")
// assertionFailure("Context Conflict Detected: is there ambigious data in your incoming sources?\nConflict:\(conflict)")
assertionFailure("Context Conflict Detected: is there ambigious data in your incoming sources?\nConflict:\(conflict)")
}
}
}

View File

@@ -505,13 +505,14 @@ cxIAAYagXxAPTlNLZXllZEFyY2hpdmVy0QAIAAlUcm9vdIABrxESLAALAAwAGQA1ADYANwBjAGQAZQBm
<relationship name="relationshipmappings" type="0/0" destination="XDDEVRELATIONSHIPMAPPING" idrefs="z142 z119 z223 z116 z229 z139 z236 z136 z214 z216"></relationship>
</object>
<object type="XDDEVENTITYMAPPING" id="z198">
<attribute name="migrationpolicyclassname" type="string"></attribute>
<attribute name="sourcename" type="string">AppPermission</attribute>
<attribute name="mappingtypename" type="string">Undefined</attribute>
<attribute name="mappingnumber" type="int16">17</attribute>
<attribute name="destinationname" type="string">AppPermission</attribute>
<attribute name="autogenerateexpression" type="bool">1</attribute>
<relationship name="mappingmodel" type="1/1" destination="XDDEVMAPPINGMODEL" idrefs="z192"></relationship>
<relationship name="attributemappings" type="0/0" destination="XDDEVATTRIBUTEMAPPING" idrefs="z213 z240 z191 z123 z287"></relationship>
<relationship name="attributemappings" type="0/0" destination="XDDEVATTRIBUTEMAPPING" idrefs="z123 z213 z287 z240 z191"></relationship>
<relationship name="relationshipmappings" type="0/0" destination="XDDEVRELATIONSHIPMAPPING" idrefs="z288"></relationship>
</object>
<object type="XDDEVENTITYMAPPING" id="z199">

View File

@@ -108,7 +108,7 @@ class StoreApp11To17MigrationPolicy: NSEntityMigrationPolicy {
// Create a new ReleaseTrack entity
let context = dInstance.managedObjectContext!
let releaseTrack = NSEntityDescription.insertNewObject(forEntityName: ReleaseTrack.entity().name! ?? ReleaseTrack.description(), into: context)
let releaseTrack = NSEntityDescription.insertNewObject(forEntityName: ReleaseTrack.entity().name ?? ReleaseTrack.description(), into: context)
releaseTrack.setValue(defaultChannel, forKey: #keyPath(ReleaseTrack._track))
// Connect the releaseTrack to the destination StoreApp

View File

@@ -1,5 +1,5 @@
//
// AppPermission11To17_1MigrationPolicy.swift
// AppPermission17to17_1MigrationPolicy.swift
// AltStore
//
// Created by Magesh K on 15/03/25.
@@ -9,13 +9,20 @@
import CoreData
@objc(AppPermission11To17_1MigrationPolicy)
class AppPermission11To17_1MigrationPolicy: AppPermission17To17_1MigrationPolicy
{
class AppPermission11To17_1MigrationPolicy: AppPermission17To17_1MigrationPolicy {
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
// Let the default implementation create the basic destination AppPermission
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
}
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
override func createRelationships(
forDestination dInstance: NSManagedObject,
in mapping: NSEntityMapping,
manager: NSMigrationManager
) throws {
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
}
}

View File

@@ -9,13 +9,18 @@
import CoreData
@objc(ReleaseTrack11To17_1MigrationPolicy)
class ReleaseTrack11To17_1MigrationPolicy: ReleaseTrack17To17_1MigrationPolicy
{
class ReleaseTrack11To17_1MigrationPolicy: ReleaseTrack17To17_1MigrationPolicy {
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
}
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
override func createRelationships(
forDestination dInstance: NSManagedObject,
in mapping: NSEntityMapping,
manager: NSMigrationManager
) throws {
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
}
}

View File

@@ -1,5 +1,5 @@
//
// Source17To17_1MigrationPolicy.swift
// Source11To17_1MigrationPolicy.swift
// AltStore
//
// Created by Magesh K on 15/03/25.
@@ -12,10 +12,12 @@ import CoreData
class Source11To17_1MigrationPolicy: Source17To17_1MigrationPolicy
{
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
// Let the default implementation create the basic destination AppPermission
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
}
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws
{
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
}
}

View File

@@ -8,14 +8,55 @@
import CoreData
fileprivate extension NSManagedObject
{
var storeAppReleaseTracks: NSOrderedSet? {
let tracks = self.value(forKey: #keyPath(StoreApp._releaseTracks)) as? NSOrderedSet
return tracks
}
}
@objc(StoreApp11To17_1MigrationPolicy)
class StoreApp11To17_1MigrationPolicy: StoreApp11To17MigrationPolicy
{
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
}
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws
{
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
let appBundleID = dInstance.value(forKey: #keyPath(StoreApp.bundleIdentifier))
let sourceID = dInstance.value(forKey: #keyPath(StoreApp.sourceIdentifier))
for case let track as NSManagedObject in dInstance.storeAppReleaseTracks ?? []
{
track.setValue(appBundleID, forKey: #keyPath(ReleaseTrack._appBundleID))
track.setValue(sourceID, forKey: #keyPath(ReleaseTrack._sourceID))
guard let releases = track.value(forKey: #keyPath(ReleaseTrack._releases)) as? NSOrderedSet else {
continue
}
for case let version as NSManagedObject in releases {
version.setValue(appBundleID, forKey: #keyPath(AppVersion.appBundleID))
version.setValue(sourceID, forKey: #keyPath(AppVersion.sourceID))
}
}
if let permissions = dInstance.value(forKey: #keyPath(StoreApp._permissions)) as? NSSet {
for case let permission as NSManagedObject in permissions {
permission.setValue(appBundleID, forKey: #keyPath(AppPermission.appBundleID))
permission.setValue(sourceID, forKey: #keyPath(AppPermission.sourceID))
}
}
if let screenshots = dInstance.value(forKey: #keyPath(StoreApp._screenshots)) as? NSOrderedSet {
for case let screenshot as NSManagedObject in screenshots {
screenshot.setValue(appBundleID, forKey: #keyPath(AppScreenshot.appBundleID))
screenshot.setValue(sourceID, forKey: #keyPath(AppScreenshot.sourceID))
}
}
}
}

View File

@@ -1,5 +1,5 @@
//
// AppPermission17To17_1MigrationPolicy.swift
// AppPermission17to17_1MigrationPolicy.swift
// AltStore
//
// Created by Magesh K on 15/03/25.
@@ -23,9 +23,12 @@ class AppPermission17To17_1MigrationPolicy: NSEntityMigrationPolicy {
// Extract the type value from source
if let type = sInstance.value(forKey: #keyPath(AppPermission.type)) as? String {
// In the new model, "permission" is the actual permission string, which needs to be derived from the old "type"
let permission = self.derivePermissionFromType(type)
destinationPermission.setValue(permission, forKey: #keyPath(AppPermission._permission))
// this is for backwards compatibility <0.5.10
// if older type was a valid permission, then consider it as "privacy" in the newer "type".
if let permission = self.derivePermissionFromType(type) {
destinationPermission.setValue(permission, forKey: #keyPath(AppPermission._permission))
destinationPermission.setValue("privacy", forKey: #keyPath(AppPermission.type))
}
}
// set initial values copied from source as-is
@@ -48,16 +51,12 @@ class AppPermission17To17_1MigrationPolicy: NSEntityMigrationPolicy {
in mapping: NSEntityMapping,
manager: NSMigrationManager
) throws {
// Retrieve the corresponding source instance for the destination StoreApp
let sourceInstances = manager.sourceInstances(forEntityMappingName: mapping.name, destinationInstances: [dInstance])
guard let sInstance = sourceInstances.first else {
print("No source instance found for destination: \(dInstance)")
return
}
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
// Retrieve the source storeApp from the source appPermission
guard let storeApp = sInstance.value(forKey: #keyPath(AppPermission.app)) as? NSManagedObject else {
print("Source \(AppPermission.description()) has no storeApp")
guard let storeApp = dInstance.value(forKey: #keyPath(AppPermission.app)) as? NSManagedObject else {
print("Destination \(AppPermission.description()) has no storeApp")
return
}
@@ -73,7 +72,8 @@ class AppPermission17To17_1MigrationPolicy: NSEntityMigrationPolicy {
}
// Helper method to derive permission string from type
private func derivePermissionFromType(_ type: String) -> String {
private func derivePermissionFromType(_ type: String) -> String? {
// Based on the code in the documents, we need to map the ALTAppPermissionType to permission strings
switch type {
case "photos": return "NSPhotosUsageDescription"
@@ -93,9 +93,8 @@ class AppPermission17To17_1MigrationPolicy: NSEntityMigrationPolicy {
case "faceID": return "NSFaceIDUsageDescription"
case "siri": return "NSSiriUsageDescription"
case "motion": return "NSMotionUsageDescription"
case "entitlement": return type // For entitlements, we might keep the raw value
case "privacy": return type // For privacy permissions, we might keep the raw value
default: return type // Default fallback
default: return nil // Default fallback
}
}
}

View File

@@ -12,29 +12,7 @@ import CoreData
class ReleaseTrack17To17_1MigrationPolicy: NSEntityMigrationPolicy {
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
// Let the default implementation create the basic destination AppPermission
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
// Get the destination AppPermission instance that was created
guard let destinationPermission = manager.destinationInstances(forEntityMappingName: mapping.name, sourceInstances: [sInstance]).first else {
print("Failed to locate destination ReleaseTrack instance")
return
}
if let track = sInstance.value(forKey: #keyPath(ReleaseTrack._track)) as? String {
destinationPermission.setValue(track, forKey: #keyPath(ReleaseTrack._track))
}
// set initial values migrated from source as-is
if let storeApp = sInstance.value(forKey: #keyPath(ReleaseTrack.storeApp)) as? NSManagedObject{
if let appBundle = storeApp.value(forKey: #keyPath(StoreApp.bundleIdentifier)) as? String{
destinationPermission.setValue(appBundle, forKey: #keyPath(ReleaseTrack._appBundleID))
}
if let sourceID = storeApp.value(forKey: #keyPath(StoreApp.sourceIdentifier)) as? String {
destinationPermission.setValue(sourceID, forKey: #keyPath(ReleaseTrack._sourceID))
}
}
}
@@ -43,16 +21,12 @@ class ReleaseTrack17To17_1MigrationPolicy: NSEntityMigrationPolicy {
in mapping: NSEntityMapping,
manager: NSMigrationManager
) throws {
// Retrieve the corresponding source instance for the destination StoreApp
let sourceInstances = manager.sourceInstances(forEntityMappingName: mapping.name, destinationInstances: [dInstance])
guard let sInstance = sourceInstances.first else {
print("No source instance found for destination: \(dInstance)")
return
}
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
// Retrieve the source storeApp from the source ReleaseTrack
guard let storeApp = sInstance.value(forKey: #keyPath(ReleaseTrack.storeApp)) as? NSManagedObject else {
print("Source \(ReleaseTrack.description()) has no storeApp")
guard let storeApp = dInstance.value(forKey: #keyPath(ReleaseTrack.storeApp)) as? NSManagedObject else {
print("Destination \(ReleaseTrack.description()) has no storeApp")
return
}
@@ -66,5 +40,4 @@ class ReleaseTrack17To17_1MigrationPolicy: NSEntityMigrationPolicy {
dInstance.setValue(sourceID, forKey: #keyPath(ReleaseTrack._sourceID))
}
}
}

View File

@@ -156,29 +156,35 @@ class Source17To17_1MigrationPolicy: NSEntityMigrationPolicy
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
guard let sourceID = dInstance.sourceSourceId else { return }
for case let newsItem as NSManagedObject in dInstance.sourceNewsItems ?? []
{
newsItem.setNewsItemSourceID(sourceID)
}
for case let app as NSManagedObject in dInstance.sourceApps ?? []
{
app.setStoreAppSourceID(sourceID)
for case let version as NSManagedObject in app.storeAppVersions ?? []
{
version.setAppVersionSourceID(sourceID)
}
for case let permission as NSManagedObject in app.storeAppPermissions ?? []
{
permission.setAppPermissionSourceID(sourceID)
}
for case let screenshot as NSManagedObject in app.storeAppScreenshots ?? []
{
screenshot.setAppScreenshotSourceID(sourceID)
}
for case let tracks as NSManagedObject in app.storeAppReleaseTracks ?? []
for case let track as NSManagedObject in app.storeAppReleaseTracks ?? []
{
tracks.setReleaseTracksSourceID(sourceID)
// print("Source_17_1MigrationPolicy: processing track \(track.value(forKey: "track")!)")
track.setValue(sourceID, forKey: #keyPath(ReleaseTrack._sourceID))
guard let releases = track.value(forKey: #keyPath(ReleaseTrack._releases)) as? NSOrderedSet else {
// print("Source_17_1MigrationPolicy: releases not found for track: \(track.value(forKey: "track")!)")
continue
}
for case let version as NSManagedObject in releases {
// print("Source_17_1MigrationPolicy: updating sourceID for version: \(version.value(forKey: "version")!)")
version.setValue(sourceID, forKey: #keyPath(AppVersion.sourceID))
}
}
}
}

View File

@@ -0,0 +1,57 @@
//
// StoreApp17To17_1MigrationPolicy.swift
// AltStore
//
// Created by Magesh K on 15/03/25.
// Copyright © 2025 SideStore. All rights reserved.
//
import CoreData
fileprivate extension NSManagedObject
{
var storeAppReleaseTracks: NSOrderedSet? {
let tracks = self.value(forKey: #keyPath(StoreApp._releaseTracks)) as? NSOrderedSet
return tracks
}
}
@objc(StoreApp17To17_1MigrationPolicy)
class StoreApp17To17_1MigrationPolicy: NSEntityMigrationPolicy
{
override func createDestinationInstances(forSource sInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws {
try super.createDestinationInstances(forSource: sInstance, in: mapping, manager: manager)
}
override func createRelationships(forDestination dInstance: NSManagedObject, in mapping: NSEntityMapping, manager: NSMigrationManager) throws
{
try super.createRelationships(forDestination: dInstance, in: mapping, manager: manager)
let appBundleID = dInstance.value(forKey: #keyPath(StoreApp.bundleIdentifier))
for case let track as NSManagedObject in dInstance.storeAppReleaseTracks ?? []
{
track.setValue(appBundleID, forKey: #keyPath(ReleaseTrack._appBundleID))
guard let releases = track.value(forKey: #keyPath(ReleaseTrack._releases)) as? NSOrderedSet else {
continue
}
for case let version as NSManagedObject in releases {
version.setValue(appBundleID, forKey: #keyPath(AppVersion.appBundleID))
}
}
if let permissions = dInstance.value(forKey: #keyPath(StoreApp._permissions)) as? NSSet {
for case let permission as NSManagedObject in permissions {
permission.setValue(appBundleID, forKey: #keyPath(AppPermission.appBundleID))
}
}
if let screenshots = dInstance.value(forKey: #keyPath(StoreApp._screenshots)) as? NSOrderedSet {
for case let screenshot as NSManagedObject in screenshots {
screenshot.setValue(appBundleID, forKey: #keyPath(AppScreenshot.appBundleID))
}
}
}
}

View File

@@ -75,6 +75,9 @@ public class NewsItem: BaseEntity, Decodable
self.imageURL = try container.decodeIfPresent(URL.self, forKey: .imageURL)
self.externalURL = try container.decodeIfPresent(URL.self, forKey: .externalURL)
// TODO: app specific news should be moved to appEntity (via refactoring)
// This can be done by: 1. having two newsItem schema, one for source and one for StoreApp
// 2. move this appID field into the newItem schema which is under StoreApp.
self.appID = try container.decodeIfPresent(String.self, forKey: .appID)
let notify = try container.decodeIfPresent(Bool.self, forKey: .notify) ?? false

View File

@@ -91,7 +91,7 @@ public extension ReleaseTrack{
}
// update it into the appVersion
_ = version.mutateForData(channel: track, appBundleID: storeApp.bundleIdentifier)
_ = version.mutateForData(channel: track, appBundleID: storeApp.bundleIdentifier, sourceID: storeApp.sourceIdentifier)
}
}