mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-19 07:27:02 +01:00
migration utilities (beta)
This commit is contained in:
@@ -35,12 +35,12 @@ public extension DataStack {
|
||||
/**
|
||||
Checks if the store with the specified filename and configuration needs to be migrated to the `DataStack`'s managed object model version.
|
||||
|
||||
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
:param: mappingModelBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
public func needsMigrationForSQLiteStore(fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as! [NSBundle]) -> MigrationType? {
|
||||
public func needsMigrationForSQLiteStore(fileName: String, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) -> MigrationType? {
|
||||
|
||||
return needsMigrationForSQLiteStore(
|
||||
fileURL: applicationSupportDirectory.URLByAppendingPathComponent(
|
||||
@@ -55,68 +55,72 @@ public extension DataStack {
|
||||
/**
|
||||
Checks if the store at the specified file URL and configuration needs to be migrated to the `DataStack`'s managed object model version.
|
||||
|
||||
:param: fileURL the local file URL for the SQLite persistent store.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
:param: mappingModelBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter mappingModelBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.allBundles()`.
|
||||
:return: a `MigrationType` indicating the type of migration required for the store; or `nil` if either inspection of the store failed, or no mapping model was found/inferred. `MigrationType` acts as a `Bool` and evaluates to `false` if no migration is required, and `true` if either a lightweight or custom migration is needed.
|
||||
*/
|
||||
public func needsMigrationForSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as! [NSBundle]) -> MigrationType? {
|
||||
public func needsMigrationForSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, mappingModelBundles: [NSBundle] = NSBundle.allBundles() as [NSBundle]) -> MigrationType? {
|
||||
|
||||
var error: NSError?
|
||||
let metadata: [NSObject : AnyObject]! = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
error: &error
|
||||
)
|
||||
if metadata == nil {
|
||||
let metadata: [String : AnyObject]
|
||||
do {
|
||||
|
||||
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\"."
|
||||
error as NSError,
|
||||
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\"."
|
||||
)
|
||||
return nil
|
||||
}
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
let destinationModel = coordinator.managedObjectModel
|
||||
if destinationModel.isConfiguration(
|
||||
configuration,
|
||||
compatibleWithStoreMetadata: metadata) {
|
||||
|
||||
return .None
|
||||
if destinationModel.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) {
|
||||
|
||||
return .None
|
||||
}
|
||||
|
||||
let sourceModel = NSManagedObjectModel(
|
||||
byMergingModels: [destinationModel],
|
||||
forStoreMetadata: metadata
|
||||
)!
|
||||
guard let sourceModel = NSManagedObjectModel(byMergingModels: [destinationModel], forStoreMetadata: metadata) else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
if NSMappingModel(
|
||||
if let _ = NSMappingModel(
|
||||
fromBundles: mappingModelBundles,
|
||||
forSourceModel: sourceModel,
|
||||
destinationModel: destinationModel) != nil {
|
||||
destinationModel: destinationModel) {
|
||||
|
||||
return .Heavyweight
|
||||
}
|
||||
|
||||
if NSMappingModel.inferredMappingModelForSourceModel(
|
||||
sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
error: nil) != nil {
|
||||
|
||||
return .Lightweight
|
||||
do {
|
||||
|
||||
try NSMappingModel.inferredMappingModelForSourceModel(
|
||||
sourceModel,
|
||||
destinationModel: destinationModel
|
||||
)
|
||||
|
||||
return .Lightweight
|
||||
}
|
||||
catch {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
Migrates an SQLite store with the specified filename to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
|
||||
|
||||
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
:param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
:param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
*/
|
||||
public func upgradeSQLiteStoreIfNeeded(fileName: String, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) -> MigrationType? {
|
||||
|
||||
@@ -134,102 +138,129 @@ public extension DataStack {
|
||||
/**
|
||||
Migrates an SQLite store at the specified file URL and configuration name to the `DataStack`'s managed object model version. This method does NOT add the migrated store to the data stack.
|
||||
|
||||
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
:param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
:param: sourceBundles an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil` which indicates the "Default" configuration.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
- parameter sourceBundles: an optional array of bundles to search mapping model files from. If not set, defaults to the `NSBundle.mainBundle()`.
|
||||
*/
|
||||
public func upgradeSQLiteStoreIfNeeded(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) -> MigrationType? {
|
||||
public func upgradeSQLiteStoreIfNeeded(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (MigrationResult) -> Void) -> MigrationType? {
|
||||
|
||||
var metadataError: NSError?
|
||||
let metadata: [NSObject: AnyObject]! = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
error: &metadataError
|
||||
)
|
||||
if metadata == nil {
|
||||
let metadata: [String: AnyObject]
|
||||
do {
|
||||
|
||||
let error = metadataError ?? NSError(coreStoreErrorCode: .UnknownError)
|
||||
metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
let metadataError = error as NSError
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\"."
|
||||
metadataError,
|
||||
"Failed to load SQLite \(typeName(NSPersistentStore)) metadata at \"\(fileURL)\"."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(MigrationResult(error))
|
||||
completion(MigrationResult(metadataError))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
let destinationModel = coordinator.managedObjectModel
|
||||
if destinationModel.isConfiguration(
|
||||
configuration,
|
||||
compatibleWithStoreMetadata: metadata) {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(MigrationResult(.None))
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
let sourceModel = NSManagedObjectModel(
|
||||
byMergingModels: [destinationModel],
|
||||
forStoreMetadata: metadata
|
||||
)!
|
||||
|
||||
if let mappingModel = NSMappingModel(
|
||||
fromBundles: sourceBundles,
|
||||
forSourceModel: sourceModel,
|
||||
destinationModel: destinationModel) {
|
||||
|
||||
self.startMigrationForSQLiteStore(
|
||||
fileURL,
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
mappingModel: mappingModel,
|
||||
migrationType: .Heavyweight,
|
||||
completion: completion
|
||||
)
|
||||
return .Heavyweight
|
||||
}
|
||||
|
||||
if let mappingModel = NSMappingModel.inferredMappingModelForSourceModel(
|
||||
sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
error: nil) {
|
||||
|
||||
self.startMigrationForSQLiteStore(
|
||||
fileURL,
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
mappingModel: mappingModel,
|
||||
migrationType: .Lightweight,
|
||||
completion: completion
|
||||
)
|
||||
return .Lightweight
|
||||
}
|
||||
|
||||
CoreStore.handleError(
|
||||
NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to load an <\(NSMappingModel.self)> for migration from version model \"\(sourceModel)\" to version model \"\(destinationModel)\"."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
guard let migrationSteps = self.computeMigrationFromStoreMetadata(metadata, configuration: configuration, sourceBundles: sourceBundles) else {
|
||||
|
||||
completion(MigrationResult(.MappingModelNotFound))
|
||||
CoreStore.handleError(
|
||||
NSError(coreStoreErrorCode: .MappingModelNotFound),
|
||||
"Failed to find migration steps from the store at URL \"\(fileURL)\" to version model \"\(model)\"."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(MigrationResult(.MappingModelNotFound))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return nil
|
||||
|
||||
if migrationSteps.count == 0 {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(MigrationResult(.None))
|
||||
}
|
||||
return .None
|
||||
}
|
||||
|
||||
var mergedMigrationType = MigrationType.None
|
||||
var migrationResult: MigrationResult?
|
||||
|
||||
var operations = [NSOperation]()
|
||||
var cancelled = false
|
||||
for (sourceModel, destinationModel, mappingModel, migrationType) in migrationSteps {
|
||||
|
||||
switch (mergedMigrationType, migrationType) {
|
||||
|
||||
case (.None, _), (.Lightweight, .Heavyweight):
|
||||
mergedMigrationType = migrationType
|
||||
|
||||
default:
|
||||
break
|
||||
}
|
||||
|
||||
operations.append(
|
||||
NSBlockOperation { [weak self] in
|
||||
|
||||
guard let strongSelf = self where !cancelled else {
|
||||
|
||||
return
|
||||
}
|
||||
|
||||
autoreleasepool {
|
||||
|
||||
do {
|
||||
|
||||
try strongSelf.startMigrationForSQLiteStore(
|
||||
fileURL: fileURL,
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
mappingModel: mappingModel
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
migrationResult = MigrationResult(error as NSError)
|
||||
cancelled = true
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
let migrationOperation = NSBlockOperation()
|
||||
migrationOperation.qualityOfService = .Utility
|
||||
operations.map { migrationOperation.addDependency($0) }
|
||||
migrationOperation.addExecutionBlock { () -> Void in
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(migrationResult ?? MigrationResult(mergedMigrationType))
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
operations.append(migrationOperation)
|
||||
|
||||
self.migrationQueue.addOperations(operations, waitUntilFinished: false)
|
||||
|
||||
return mergedMigrationType
|
||||
}
|
||||
|
||||
/**
|
||||
Asynchronously adds to the stack an SQLite store from the given SQLite file name. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
:param: fileName the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
:param: completion the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
|
||||
- parameter fileName: the local filename for the SQLite persistent store in the "Application Support" directory. A new SQLite file will be created if it does not exist. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileName` explicitly for each of them.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
|
||||
*/
|
||||
public func addSQLiteStore(fileName: String, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) {
|
||||
|
||||
@@ -247,39 +278,16 @@ public extension DataStack {
|
||||
/**
|
||||
Asynchronously adds to the stack an SQLite store from the given SQLite file URL. Note that using `addSQLiteStore(...)` instead of `addSQLiteStoreAndWait(...)` implies that the migrations are allowed and expected (thus the asynchronous `completion`.)
|
||||
|
||||
:param: fileURL the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
:param: configuration an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
:param: completion the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
|
||||
- parameter fileURL: the local file URL for the SQLite persistent store. A new SQLite file will be created if it does not exist. If not specified, defaults to a file URL pointing to a "<Application name>.sqlite" file in the "Application Support" directory. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter configuration: an optional configuration name from the model file. If not specified, defaults to `nil`, the "Default" configuration. Note that if you have multiple configurations, you will need to specify a different `fileURL` explicitly for each of them.
|
||||
- parameter completion: the closure to be executed on the main queue when the process completes, either due to success or failure. The closure's `PersistentStoreResult` argument indicates the result.
|
||||
*/
|
||||
public func addSQLiteStore(fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = nil, completion: (PersistentStoreResult) -> Void) {
|
||||
|
||||
if NSFileManager.defaultManager().fileExistsAtPath(fileURL.path!) {
|
||||
|
||||
var error: NSError?
|
||||
let metadata = NSPersistentStoreCoordinator.metadataForPersistentStoreOfType(
|
||||
NSSQLiteStoreType,
|
||||
URL: fileURL,
|
||||
error: &error
|
||||
)
|
||||
if metadata == nil {
|
||||
|
||||
CoreStore.handleError(
|
||||
error ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to load SQLite <\(NSPersistentStore.self)> metadata at \"\(fileURL)\"."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(.UnknownError))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
public func addSQLiteStore(fileURL fileURL: NSURL = defaultSQLiteStoreURL, configuration: String? = nil, sourceBundles: [NSBundle]? = NSBundle.allBundles(), completion: (PersistentStoreResult) -> Void) {
|
||||
|
||||
let coordinator = self.coordinator;
|
||||
if let store = coordinator.persistentStoreForURL(fileURL) {
|
||||
|
||||
let isExistingStoreAutomigrating = ((store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool) ?? false)
|
||||
let isExistingStoreAutomigrating = store.options?[NSMigratePersistentStoresAutomaticallyOption] as? Bool == true
|
||||
|
||||
if store.type == NSSQLiteStoreType
|
||||
&& isExistingStoreAutomigrating
|
||||
@@ -294,7 +302,7 @@ public extension DataStack {
|
||||
|
||||
CoreStore.handleError(
|
||||
NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL),
|
||||
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\" because a different <\(NSPersistentStore.self)> at that URL already exists."
|
||||
"Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
@@ -304,151 +312,239 @@ public extension DataStack {
|
||||
return
|
||||
}
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
var directoryError: NSError?
|
||||
if !fileManager.createDirectoryAtURL(
|
||||
fileURL.URLByDeletingLastPathComponent!,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil,
|
||||
error: &directoryError) {
|
||||
do {
|
||||
|
||||
try NSFileManager.defaultManager().createDirectoryAtURL(
|
||||
fileURL.URLByDeletingLastPathComponent!,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
self.upgradeSQLiteStoreIfNeeded(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
sourceBundles: sourceBundles,
|
||||
completion: { (result) -> Void in
|
||||
|
||||
CoreStore.handleError(
|
||||
directoryError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to create directory for SQLite store at \"\(fileURL)\"."
|
||||
if case .Failure(let error) = result
|
||||
where error.domain != NSCocoaErrorDomain || error.code != NSFileReadNoSuchFileError {
|
||||
|
||||
completion(PersistentStoreResult(error))
|
||||
return
|
||||
}
|
||||
|
||||
let persistentStoreResult = self.addSQLiteStoreAndWait(
|
||||
fileURL: fileURL,
|
||||
configuration: configuration,
|
||||
automigrating: false,
|
||||
resetStoreOnMigrationFailure: false
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(PersistentStoreResult(directoryError!))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
coordinator.performBlock {
|
||||
|
||||
var persistentStoreError: NSError?
|
||||
let store = coordinator.addPersistentStoreWithType(
|
||||
NSSQLiteStoreType,
|
||||
configuration: configuration,
|
||||
URL: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["WAL": "journal_mode"],
|
||||
NSInferMappingModelAutomaticallyOption: true,
|
||||
NSMigratePersistentStoresAutomaticallyOption: true],
|
||||
error: &persistentStoreError)
|
||||
|
||||
if let store = store {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
self.updateMetadataForPersistentStore(store)
|
||||
completion(PersistentStoreResult(store))
|
||||
}
|
||||
completion(persistentStoreResult)
|
||||
}
|
||||
else {
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
CoreStore.handleError(
|
||||
persistentStoreError ?? NSError(coreStoreErrorCode: .UnknownError),
|
||||
"Failed to add SQLite <\(NSPersistentStore.self)> at \"\(fileURL)\"."
|
||||
)
|
||||
|
||||
completion(PersistentStoreResult(.UnknownError))
|
||||
}
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
// MARK: Private
|
||||
|
||||
private func startMigrationForSQLiteStore(fileURL: NSURL, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType, completion: (MigrationResult) -> Void) {
|
||||
private func computeMigrationFromStoreMetadata(metadata: [String: AnyObject], configuration: String? = nil, sourceBundles: [NSBundle]? = nil) -> [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]? {
|
||||
|
||||
let model = self.model
|
||||
if model.isConfiguration(configuration, compatibleWithStoreMetadata: metadata) {
|
||||
|
||||
return []
|
||||
}
|
||||
|
||||
let metadataModel = NSManagedObjectModel(byMergingModels: model.mergedModels(), forStoreMetadata: metadata)!
|
||||
if let bypassModel = NSMappingModel(
|
||||
fromBundles: sourceBundles,
|
||||
forSourceModel: metadataModel,
|
||||
destinationModel: model) {
|
||||
|
||||
return [
|
||||
(
|
||||
sourceModel: metadataModel,
|
||||
destinationModel: model,
|
||||
mappingModel: bypassModel,
|
||||
migrationType: .Heavyweight
|
||||
)
|
||||
]
|
||||
}
|
||||
|
||||
var initialModel: NSManagedObjectModel?
|
||||
if let modelHashes = metadata[NSStoreModelVersionHashesKey] as? [String : NSData],
|
||||
let modelVersions = model.modelVersions {
|
||||
|
||||
for modelVersion in modelVersions {
|
||||
|
||||
if let versionModel = model[modelVersion] where modelHashes == versionModel.entityVersionHashesByName {
|
||||
|
||||
initialModel = versionModel
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
guard var currentVersion = initialModel?.currentModelVersion else {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
let migrationChain = self.migrationChain
|
||||
var migrationSteps = [(sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel, migrationType: MigrationType)]()
|
||||
|
||||
while let nextVersion = migrationChain.nextVersionFrom(currentVersion),
|
||||
let sourceModel = model[currentVersion],
|
||||
let destinationModel = model[nextVersion] {
|
||||
|
||||
if let mappingModel = NSMappingModel(
|
||||
fromBundles: sourceBundles,
|
||||
forSourceModel: sourceModel,
|
||||
destinationModel: destinationModel) {
|
||||
|
||||
migrationSteps.append(
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
mappingModel: mappingModel,
|
||||
migrationType: .Heavyweight
|
||||
)
|
||||
}
|
||||
else {
|
||||
|
||||
do {
|
||||
|
||||
let mappingModel = try NSMappingModel.inferredMappingModelForSourceModel(
|
||||
sourceModel,
|
||||
destinationModel: destinationModel
|
||||
)
|
||||
|
||||
migrationSteps.append(
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel,
|
||||
mappingModel: mappingModel,
|
||||
migrationType: .Lightweight
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
currentVersion = nextVersion
|
||||
}
|
||||
|
||||
if migrationSteps.last?.destinationModel == model {
|
||||
|
||||
return migrationSteps
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
private func startMigrationForSQLiteStore(fileURL fileURL: NSURL, sourceModel: NSManagedObjectModel, destinationModel: NSManagedObjectModel, mappingModel: NSMappingModel) throws {
|
||||
|
||||
let migrationManager = NSMigrationManager(
|
||||
sourceModel: sourceModel,
|
||||
destinationModel: destinationModel
|
||||
)
|
||||
|
||||
self.migrationQueue.async {
|
||||
|
||||
var lastReportedProgress: Float = -1
|
||||
let timer = GCDTimer.createSuspended(
|
||||
.Main,
|
||||
interval: 0.1,
|
||||
eventHandler: { (timer) -> Void in
|
||||
var lastReportedProgress: Float = -1
|
||||
let timer = GCDTimer.createSuspended(
|
||||
.Main,
|
||||
interval: 0.1,
|
||||
eventHandler: { (timer) -> Void in
|
||||
|
||||
let progress = migrationManager.migrationProgress
|
||||
if progress > lastReportedProgress {
|
||||
|
||||
let progress = migrationManager.migrationProgress
|
||||
if progress > lastReportedProgress {
|
||||
|
||||
// TODO: progress
|
||||
CoreStore.log(.Trace, message: "migration progress: \(progress)")
|
||||
lastReportedProgress = progress
|
||||
}
|
||||
// TODO: progress
|
||||
CoreStore.log(.Trace, message: "migration progress: \(progress)")
|
||||
lastReportedProgress = progress
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
|
||||
let temporaryDirectoryURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true).URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString)
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
try! fileManager.createDirectoryAtURL(
|
||||
temporaryDirectoryURL,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
do {
|
||||
|
||||
let temporaryFileURL = NSURL(fileURLWithPath: NSTemporaryDirectory(), isDirectory: true)!.URLByAppendingPathComponent(NSProcessInfo().globallyUniqueString)
|
||||
|
||||
var migrationError: NSError?
|
||||
let migrationCompleted = migrationManager.migrateStoreFromURL(
|
||||
try migrationManager.migrateStoreFromURL(
|
||||
fileURL,
|
||||
type: NSSQLiteStoreType,
|
||||
options: nil,
|
||||
options: [
|
||||
NSSQLitePragmasOption: ["WAL": "journal_mode"]
|
||||
],
|
||||
withMappingModel: mappingModel,
|
||||
toDestinationURL: temporaryFileURL,
|
||||
toDestinationURL: temporaryDirectoryURL.URLByAppendingPathComponent(fileURL.lastPathComponent!, isDirectory: false),
|
||||
destinationType: NSSQLiteStoreType,
|
||||
destinationOptions: nil,
|
||||
error: &migrationError
|
||||
destinationOptions: [
|
||||
NSSQLitePragmasOption: ["WAL": "journal_mode"],
|
||||
NSSQLiteManualVacuumOption: true
|
||||
]
|
||||
)
|
||||
}
|
||||
catch {
|
||||
|
||||
timer.suspend()
|
||||
|
||||
let fileManager = NSFileManager.defaultManager()
|
||||
if !migrationCompleted {
|
||||
do {
|
||||
|
||||
fileManager.removeItemAtURL(temporaryFileURL, error: nil)
|
||||
try fileManager.removeItemAtURL(temporaryDirectoryURL)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
let migrationError = error as NSError
|
||||
CoreStore.handleError(
|
||||
migrationError,
|
||||
"Failed to migrate from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"."
|
||||
)
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
timer.suspend()
|
||||
|
||||
do {
|
||||
|
||||
let originalDirectoryURL = fileURL.URLByDeletingLastPathComponent!
|
||||
for temporaryFileURL in try fileManager.contentsOfDirectoryAtURL(temporaryDirectoryURL, includingPropertiesForKeys: nil, options: .SkipsSubdirectoryDescendants) {
|
||||
|
||||
let error = migrationError ?? NSError(coreStoreErrorCode: .UnknownError)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to migrate from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"."
|
||||
try fileManager.replaceItemAtURL(
|
||||
originalDirectoryURL.URLByAppendingPathComponent(
|
||||
temporaryFileURL.lastPathComponent!,
|
||||
isDirectory: false
|
||||
),
|
||||
withItemAtURL: temporaryFileURL,
|
||||
backupItemName: nil,
|
||||
options: [],
|
||||
resultingItemURL: nil
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(MigrationResult(error))
|
||||
}
|
||||
return
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
var replaceError: NSError?
|
||||
if !fileManager.replaceItemAtURL(
|
||||
fileURL,
|
||||
withItemAtURL: temporaryFileURL,
|
||||
backupItemName: nil,
|
||||
options: .allZeros,
|
||||
resultingItemURL: nil,
|
||||
error: &replaceError) {
|
||||
|
||||
fileManager.removeItemAtURL(temporaryFileURL, error: nil)
|
||||
|
||||
let error = replaceError ?? NSError(coreStoreErrorCode: .UnknownError)
|
||||
CoreStore.handleError(
|
||||
error,
|
||||
"Failed to save store after migrating from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"."
|
||||
)
|
||||
|
||||
GCDQueue.Main.async {
|
||||
|
||||
completion(MigrationResult(error))
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
GCDQueue.Main.async {
|
||||
do {
|
||||
|
||||
completion(MigrationResult(migrationType))
|
||||
try fileManager.removeItemAtURL(temporaryDirectoryURL)
|
||||
}
|
||||
catch _ { }
|
||||
|
||||
let replaceError = error as NSError
|
||||
CoreStore.handleError(
|
||||
replaceError,
|
||||
"Failed to save store after migrating from version model \"\(migrationManager.sourceModel)\" to version model \"\(migrationManager.destinationModel)\"."
|
||||
)
|
||||
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user