migration utilities (beta)

This commit is contained in:
John Rommel Estropia
2015-07-07 07:58:16 +09:00
parent 261c3a6001
commit bf0eebe057
7 changed files with 748 additions and 458 deletions

View File

@@ -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
}
}
}