mobile-ios/CovidSafe/EncounterDB+migration.swift
2020-05-26 17:13:26 +10:00

110 lines
4.8 KiB
Swift

//
// EncounterDB+migration.swift
// CovidSafe
//
// Copyright © 2020 Australian Government. All rights reserved.
//
import CoreData
protocol EncounterDBMigrationProgress {
func migrationBegun()
func migrationComplete()
func migrationFailed(error: Error)
}
enum MigrationError: Error {
case unableToRetrieveSourceModel
}
extension EncounterDB {
func store(_ storeURL:URL, isCompatibleWithModel model:NSManagedObjectModel) -> Bool {
do {
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: nil)
if model.isConfiguration(withName: nil, compatibleWithStoreMetadata: metadata) {
return true
}
} catch {
DLog("ERROR getting metadata from \(storeURL) \(error)")
}
DLog("The store is NOT compatible with the current version of the model")
return false
}
func migrateStoreIfNecessary (storeURL:URL, destinationModel:NSManagedObjectModel) {
guard FileManager.default.fileExists(atPath: storeURL.path) else {
return
}
guard store(storeURL, isCompatibleWithModel: destinationModel) == false else {
return
}
registerBackgroundTask()
do {
self.migrationDelegate?.migrationBegun()
let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStore(ofType: NSSQLiteStoreType, at: storeURL, options: nil)
if let sourceModel = NSManagedObjectModel.mergedModel(from: [Bundle.main], forStoreMetadata: metadata) {
//do the migration, on the background
DispatchQueue.global(qos: .background).async {
do {
DLog("STARTING MIGRATION")
try self.migrateStore(store: storeURL, sourceModel: sourceModel, destinationModel: destinationModel)
DispatchQueue.main.async {
self.endBackgroundTask()
self.migrationDelegate?.migrationComplete()
}
} catch {
DispatchQueue.main.async {
self.endBackgroundTask()
self.migrationDelegate?.migrationFailed(error: error)
}
DLog("Failed to migrate")
}
}
} else {
self.endBackgroundTask()
self.migrationDelegate?.migrationFailed(error: MigrationError.unableToRetrieveSourceModel)
}
} catch {
endBackgroundTask()
self.migrationDelegate?.migrationFailed(error: error)
print("FAILED to get metadata \(error)")
}
}
func migrateStore(store:URL, sourceModel:NSManagedObjectModel, destinationModel:NSManagedObjectModel) throws {
let tempdir = store.deletingLastPathComponent()
let tempStore = tempdir.appendingPathComponent("temp.sqlite", isDirectory: false)
let mappingModel = NSMappingModel(from: nil, forSourceModel: sourceModel, destinationModel: destinationModel)
let migrationManager = NSMigrationManager(sourceModel: sourceModel, destinationModel: destinationModel)
let options = [NSSQLitePragmasOption: ["journal_mode":"DELETE"]]
do {
try migrationManager.migrateStore(from: store,
sourceType: NSSQLiteStoreType,
options: options,
with: mappingModel,
toDestinationURL: tempStore,
destinationType: NSSQLiteStoreType,
destinationOptions: nil)
let psc = NSPersistentStoreCoordinator(managedObjectModel: sourceModel)
try psc.replacePersistentStore(at: store,
destinationOptions: nil,
withPersistentStoreFrom: tempStore,
sourceOptions: nil,
ofType: NSSQLiteStoreType)
try psc.destroyPersistentStore(at: tempStore, ofType: NSSQLiteStoreType, options: [NSSQLitePragmasOption: ["secure_delete": true]])
try FileManager.default.removeItem(at: tempStore)
DLog("SUCCESSFULLY MIGRATED \(store) to the Current Model")
} catch {
DLog("FAILED MIGRATION: \(error)")
if FileManager.default.fileExists(atPath: tempStore.path) {
try FileManager.default.removeItem(at: tempStore)
}
throw error
}
}
}