forked from jessesquires/JSQCoreDataKit
-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathMigrate.swift
More file actions
166 lines (130 loc) · 6.43 KB
/
Copy pathMigrate.swift
File metadata and controls
166 lines (130 loc) · 6.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
//
// Created by Jesse Squires
// http://www.jessesquires.com
//
//
// Documentation
// http://www.jessesquires.com/JSQCoreDataKit
//
//
// GitHub
// https://github.com/jessesquires/JSQCoreDataKit
//
//
// License
// Copyright © 2015 Jesse Squires
// Released under an MIT license: http://opensource.org/licenses/MIT
//
import CoreData
import Foundation
/**
An error type that specifies possible errors that are thrown by calling `CoreDataModel.migrate() throws`.
*/
public enum MigrationError: Error {
/**
Specifies that the `NSManagedObjectModel` corresponding to the existing persistent store was not found in the model's bundle.
- parameter model: The model that failed to be migrated.
*/
case sourceModelNotFound(model: CoreDataModel)
/**
Specifies that an `NSMappingModel` was not found in the model's bundle in the progressive migration 'path'.
- parameter sourceModel: The destination managed object model for which a mapping model was not found.
*/
case mappingModelNotFound(destinationModel: NSManagedObjectModel)
}
extension CoreDataModel {
/**
Progressively migrates the persistent store of the `CoreDataModel` based on mapping models found in the model's bundle.
If the model returns false from `needsMigration`, then this function does nothing.
- throws: If an error occurs, either an `NSError` or a `MigrationError` is thrown. If an `NSError` is thrown, it could
specify any of the following: an error checking persistent store metadata, an error from `NSMigrationManager`, or
an error from `NSFileManager`.
- warning: Migration is only supported for on-disk persistent stores.
A complete 'path' of mapping models must exist between the peristent store's version and the model's version.
*/
public func migrate() throws {
guard needsMigration else { return }
guard let storeURL = self.storeURL, let storeDirectory = storeType.storeDirectory() else {
preconditionFailure("*** Error: migration is only available for on-disk persistent stores. Invalid model: \(self)")
}
// could also throw NSError from NSPersistentStoreCoordinator
guard let sourceModel = try findCompatibleModel(withBundle: bundle, storeType: storeType.type, storeURL: storeURL) else {
throw MigrationError.sourceModelNotFound(model: self)
}
let migrationSteps = try buildMigrationMappingSteps(bundle: bundle,
sourceModel: sourceModel,
destinationModel: managedObjectModel)
for step in migrationSteps {
let tempURL = storeDirectory.appendingPathComponent("migration." + ModelFileExtension.sqlite.rawValue)
// could throw error from `migrateStoreFromURL`
let manager = NSMigrationManager(sourceModel: step.source, destinationModel: step.destination)
try manager.migrateStore(from: storeURL,
sourceType: storeType.type,
options: nil,
with: step.mapping,
toDestinationURL: tempURL,
destinationType: storeType.type,
destinationOptions: nil)
// could throw file system errors
try removeExistingStore()
try FileManager.default.moveItem(at: tempURL, to: storeURL)
}
}
}
// MARK: Internal
internal struct MigrationMappingStep {
let source: NSManagedObjectModel
let mapping: NSMappingModel
let destination: NSManagedObjectModel
}
internal func findCompatibleModel(withBundle bundle: Bundle,
storeType: String,
storeURL: URL) throws -> NSManagedObjectModel? {
let storeMetadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: storeType, at: storeURL, options: nil)
let modelsInBundle = findModelsInBundle(bundle)
for model in modelsInBundle where model.isConfiguration(withName: nil, compatibleWithStoreMetadata: storeMetadata) {
return model
}
return nil
}
internal func findModelsInBundle(_ bundle: Bundle) -> [NSManagedObjectModel] {
guard let modelBundleDirectoryURLs = bundle.urls(forResourcesWithExtension: ModelFileExtension.bundle.rawValue, subdirectory: nil) else {
return []
}
let modelBundleDirectoryNames = modelBundleDirectoryURLs.compactMap { url -> String? in
url.lastPathComponent
}
let modelVersionFileURLs = modelBundleDirectoryNames.compactMap { name -> [URL]? in
bundle.urls(forResourcesWithExtension: ModelFileExtension.versionedFile.rawValue, subdirectory: name)
}
let managedObjectModels = Array(modelVersionFileURLs.joined()).compactMap { url -> NSManagedObjectModel? in
NSManagedObjectModel(contentsOf: url)
}
return managedObjectModels
}
internal func buildMigrationMappingSteps(bundle: Bundle,
sourceModel: NSManagedObjectModel,
destinationModel: NSManagedObjectModel) throws -> [MigrationMappingStep] {
var migrationSteps = [MigrationMappingStep]()
var nextModel = sourceModel
repeat {
guard let nextStep = nextMigrationMappingStep(fromSourceModel: nextModel, bundle: bundle) else {
throw MigrationError.mappingModelNotFound(destinationModel: nextModel)
}
migrationSteps.append(nextStep)
nextModel = nextStep.destination
} while nextModel.entityVersionHashesByName != destinationModel.entityVersionHashesByName
return migrationSteps
}
internal func nextMigrationMappingStep(fromSourceModel sourceModel: NSManagedObjectModel,
bundle: Bundle) -> MigrationMappingStep? {
let modelsInBundle = findModelsInBundle(bundle)
for nextDestinationModel in modelsInBundle where nextDestinationModel.entityVersionHashesByName != sourceModel.entityVersionHashesByName {
if let mappingModel = NSMappingModel(from: [bundle],
forSourceModel: sourceModel,
destinationModel: nextDestinationModel) {
return MigrationMappingStep(source: sourceModel, mapping: mappingModel, destination: nextDestinationModel)
}
}
return nil
}