mirror of
https://github.com/JohnEstropia/CoreStore.git
synced 2026-01-15 21:53:39 +01:00
Make RecreateStoreOnModelMismatch flag work again (fixes #126)
This commit is contained in:
@@ -29,7 +29,7 @@ import CoreStore
|
||||
|
||||
// MARK: - SetupTests
|
||||
|
||||
class SetupTests: BaseTestCase {
|
||||
class SetupTests: BaseTestDataTestCase {
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatDataStacks_ConfigureCorrectly() {
|
||||
@@ -193,6 +193,62 @@ class SetupTests: BaseTestCase {
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatSQLiteStores_DeleteFilesCorrectly() {
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let sqliteStore = SQLiteStore()
|
||||
func createStore() throws -> [String: Any] {
|
||||
|
||||
do {
|
||||
|
||||
let stack = DataStack(
|
||||
modelName: "Model",
|
||||
bundle: Bundle(for: type(of: self))
|
||||
)
|
||||
try! stack.addStorageAndWait(sqliteStore)
|
||||
self.prepareTestDataForStack(stack)
|
||||
}
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
|
||||
|
||||
return try NSPersistentStoreCoordinator.metadataForPersistentStore(
|
||||
ofType: type(of: sqliteStore).storeType,
|
||||
at: sqliteStore.fileURL,
|
||||
options: sqliteStore.storeOptions
|
||||
)
|
||||
}
|
||||
do {
|
||||
|
||||
let metadata = try createStore()
|
||||
let stack = DataStack(
|
||||
modelName: "Model",
|
||||
bundle: Bundle(for: type(of: self))
|
||||
)
|
||||
try sqliteStore.eraseStorageAndWait(metadata: metadata, soureModelHint: stack.model[metadata])
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
do {
|
||||
|
||||
let metadata = try createStore()
|
||||
try sqliteStore.eraseStorageAndWait(metadata: metadata, soureModelHint: nil)
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatLegacySQLiteStores_SetupCorrectly() {
|
||||
|
||||
@@ -202,7 +258,7 @@ class SetupTests: BaseTestCase {
|
||||
)
|
||||
do {
|
||||
|
||||
let sqliteStore = SQLiteStore()
|
||||
let sqliteStore = LegacySQLiteStore()
|
||||
do {
|
||||
|
||||
try stack.addStorageAndWait(sqliteStore)
|
||||
@@ -217,7 +273,7 @@ class SetupTests: BaseTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let sqliteStore = SQLiteStore(
|
||||
let sqliteStore = LegacySQLiteStore(
|
||||
fileName: "ConfigStore1.sqlite",
|
||||
configuration: "Config1",
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
@@ -236,7 +292,7 @@ class SetupTests: BaseTestCase {
|
||||
}
|
||||
do {
|
||||
|
||||
let sqliteStore = SQLiteStore(
|
||||
let sqliteStore = LegacySQLiteStore(
|
||||
fileName: "ConfigStore2.sqlite",
|
||||
configuration: "Config2",
|
||||
localStorageOptions: .recreateStoreOnModelMismatch
|
||||
@@ -254,4 +310,60 @@ class SetupTests: BaseTestCase {
|
||||
XCTAssert(sqliteStore.matchesPersistentStore(persistentStore!))
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
dynamic func test_ThatLegacySQLiteStores_DeleteFilesCorrectly() {
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let sqliteStore = LegacySQLiteStore()
|
||||
func createStore() throws -> [String: Any] {
|
||||
|
||||
do {
|
||||
|
||||
let stack = DataStack(
|
||||
modelName: "Model",
|
||||
bundle: Bundle(for: type(of: self))
|
||||
)
|
||||
try! stack.addStorageAndWait(sqliteStore)
|
||||
self.prepareTestDataForStack(stack)
|
||||
}
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
|
||||
XCTAssertTrue(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
|
||||
|
||||
return try NSPersistentStoreCoordinator.metadataForPersistentStore(
|
||||
ofType: type(of: sqliteStore).storeType,
|
||||
at: sqliteStore.fileURL,
|
||||
options: sqliteStore.storeOptions
|
||||
)
|
||||
}
|
||||
do {
|
||||
|
||||
let metadata = try createStore()
|
||||
let stack = DataStack(
|
||||
modelName: "Model",
|
||||
bundle: Bundle(for: type(of: self))
|
||||
)
|
||||
try sqliteStore.eraseStorageAndWait(metadata: metadata, soureModelHint: stack.model[metadata])
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
do {
|
||||
|
||||
let metadata = try createStore()
|
||||
try sqliteStore.eraseStorageAndWait(metadata: metadata, soureModelHint: nil)
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path))
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-wal")))
|
||||
XCTAssertFalse(fileManager.fileExists(atPath: sqliteStore.fileURL.path.appending("-shm")))
|
||||
}
|
||||
catch {
|
||||
|
||||
XCTFail()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -312,7 +312,7 @@ public protocol LocalStorage: StorageInterface {
|
||||
var mappingModelBundles: [NSBundle] { get }
|
||||
var localStorageOptions: LocalStorageOptions { get }
|
||||
func dictionary(forOptions: LocalStorageOptions) -> [String: AnyObject]?
|
||||
func eraseStorageAndWait(soureModel: NSManagedObjectModel) throws
|
||||
func eraseStorageAndWait(metadata: [String: Any], soureModelHint: NSManagedObjectModel?) throws
|
||||
}
|
||||
```
|
||||
If you have custom `NSIncrementalStore` or `NSAtomicStore` subclasses, you can implement this protocol and use it similarly to `SQLiteStore`.
|
||||
|
||||
@@ -216,7 +216,10 @@ public extension DataStack {
|
||||
|
||||
do {
|
||||
|
||||
_ = try self.model[metadata].flatMap(storage.eraseStorageAndWait)
|
||||
try storage.eraseStorageAndWait(
|
||||
metadata: metadata,
|
||||
soureModelHint: self.model[metadata]
|
||||
)
|
||||
_ = try self.addStorageAndWait(storage)
|
||||
|
||||
DispatchQueue.main.async {
|
||||
|
||||
@@ -154,11 +154,11 @@ public final class CSSQLiteStore: NSObject, CSLocalStorage, CoreStoreObjectiveCT
|
||||
Called by the `CSDataStack` to perform actual deletion of the store file from disk. Do not call directly! The `sourceModel` argument is a hint for the existing store's model version. For `CSSQLiteStore`, this converts the database's WAL journaling mode to DELETE before deleting the file.
|
||||
*/
|
||||
@objc
|
||||
public func eraseStorageAndWait(soureModel: NSManagedObjectModel, error: NSErrorPointer) -> Bool {
|
||||
public func eraseStorageAndWait(metadata: NSDictionary, soureModelHint: NSManagedObjectModel?, error: NSErrorPointer) -> Bool {
|
||||
|
||||
return bridge(error) {
|
||||
|
||||
try self.bridgeToSwift.eraseStorageAndWait(soureModel: soureModel)
|
||||
try self.bridgeToSwift.eraseStorageAndWait(metadata: metadata as! [String: Any], soureModelHint: soureModelHint)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -121,5 +121,5 @@ public protocol CSLocalStorage: CSStorageInterface {
|
||||
Called by the `CSDataStack` to perform actual deletion of the store file from disk. Do not call directly! The `sourceModel` argument is a hint for the existing store's model version. Implementers can use the `sourceModel` to perform necessary store operations. (SQLite stores for example, can convert WAL journaling mode to DELETE before deleting)
|
||||
*/
|
||||
@objc
|
||||
func eraseStorageAndWait(soureModel: NSManagedObjectModel, error: NSErrorPointer) -> Bool
|
||||
func eraseStorageAndWait(metadata: NSDictionary, soureModelHint: NSManagedObjectModel?, error: NSErrorPointer) -> Bool
|
||||
}
|
||||
|
||||
@@ -261,7 +261,10 @@ public final class DataStack {
|
||||
at: fileURL,
|
||||
options: storeOptions
|
||||
)
|
||||
_ = try self.model[metadata].flatMap(storage.eraseStorageAndWait)
|
||||
try storage.eraseStorageAndWait(
|
||||
metadata: metadata,
|
||||
soureModelHint: self.model[metadata]
|
||||
)
|
||||
_ = try self.createPersistentStoreFromStorage(
|
||||
storage,
|
||||
finalURL: fileURL,
|
||||
|
||||
@@ -165,45 +165,69 @@ public final class LegacySQLiteStore: LocalStorage, DefaultInitializableStore {
|
||||
/**
|
||||
Called by the `DataStack` to perform actual deletion of the store file from disk. Do not call directly! The `sourceModel` argument is a hint for the existing store's model version. For `SQLiteStore`, this converts the database's WAL journaling mode to DELETE before deleting the file.
|
||||
*/
|
||||
public func eraseStorageAndWait(soureModel: NSManagedObjectModel) throws {
|
||||
public func eraseStorageAndWait(metadata: [String: Any], soureModelHint: NSManagedObjectModel?) throws {
|
||||
|
||||
// TODO: check if attached to persistent store
|
||||
|
||||
let fileURL = self.fileURL
|
||||
try autoreleasepool {
|
||||
|
||||
let journalUpdatingCoordinator = NSPersistentStoreCoordinator(managedObjectModel: soureModel)
|
||||
let store = try journalUpdatingCoordinator.addPersistentStore(
|
||||
ofType: type(of: self).storeType,
|
||||
configurationName: self.configuration,
|
||||
at: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
|
||||
)
|
||||
try journalUpdatingCoordinator.remove(store)
|
||||
func deleteFiles(storeURL: URL, extraFiles: [String] = []) throws {
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let extraFiles: [String] = [
|
||||
storeURL.path.appending("-wal"),
|
||||
storeURL.path.appending("-shm")
|
||||
]
|
||||
do {
|
||||
|
||||
let temporaryFile = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!)
|
||||
let trashURL = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!)
|
||||
.appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.CoreStore.DataStack", isDirectory: true)
|
||||
.appendingPathComponent("trash", isDirectory: true)
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: false)
|
||||
try fileManager.createDirectory(
|
||||
at: temporaryFile.deletingLastPathComponent(),
|
||||
at: trashURL,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
try fileManager.moveItem(at: fileURL, to: temporaryFile)
|
||||
|
||||
let temporaryFileURL = trashURL.appendingPathComponent(UUID().uuidString, isDirectory: false)
|
||||
try fileManager.moveItem(at: storeURL, to: temporaryFileURL)
|
||||
|
||||
let extraTemporaryFiles = extraFiles.map { (extraFile) -> String in
|
||||
|
||||
let temporaryFile = trashURL.appendingPathComponent(UUID().uuidString, isDirectory: false).path
|
||||
if let _ = try? fileManager.moveItem(atPath: extraFile, toPath: temporaryFile) {
|
||||
|
||||
return temporaryFile
|
||||
}
|
||||
return extraFile
|
||||
}
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
|
||||
_ = try? fileManager.removeItem(at: temporaryFile)
|
||||
_ = try? fileManager.removeItem(at: temporaryFileURL)
|
||||
extraTemporaryFiles.forEach({ _ = try? fileManager.removeItem(atPath: $0) })
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
try fileManager.removeItem(at: fileURL)
|
||||
try fileManager.removeItem(at: storeURL)
|
||||
extraFiles.forEach({ _ = try? fileManager.removeItem(atPath: $0) })
|
||||
}
|
||||
}
|
||||
|
||||
let fileURL = self.fileURL
|
||||
try autoreleasepool {
|
||||
|
||||
if let soureModel = soureModelHint ?? NSManagedObjectModel.mergedModel(from: nil, forStoreMetadata: metadata) {
|
||||
|
||||
let journalUpdatingCoordinator = NSPersistentStoreCoordinator(managedObjectModel: soureModel)
|
||||
let store = try journalUpdatingCoordinator.addPersistentStore(
|
||||
ofType: type(of: self).storeType,
|
||||
configurationName: self.configuration,
|
||||
at: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
|
||||
)
|
||||
try journalUpdatingCoordinator.remove(store)
|
||||
}
|
||||
try deleteFiles(storeURL: fileURL)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -162,45 +162,69 @@ public final class SQLiteStore: LocalStorage, DefaultInitializableStore {
|
||||
/**
|
||||
Called by the `DataStack` to perform actual deletion of the store file from disk. Do not call directly! The `sourceModel` argument is a hint for the existing store's model version. For `SQLiteStore`, this converts the database's WAL journaling mode to DELETE before deleting the file.
|
||||
*/
|
||||
public func eraseStorageAndWait(soureModel: NSManagedObjectModel) throws {
|
||||
public func eraseStorageAndWait(metadata: [String: Any], soureModelHint: NSManagedObjectModel?) throws {
|
||||
|
||||
// TODO: check if attached to persistent store
|
||||
|
||||
let fileURL = self.fileURL
|
||||
try autoreleasepool {
|
||||
|
||||
let journalUpdatingCoordinator = NSPersistentStoreCoordinator(managedObjectModel: soureModel)
|
||||
let store = try journalUpdatingCoordinator.addPersistentStore(
|
||||
ofType: type(of: self).storeType,
|
||||
configurationName: self.configuration,
|
||||
at: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
|
||||
)
|
||||
try journalUpdatingCoordinator.remove(store)
|
||||
func deleteFiles(storeURL: URL, extraFiles: [String] = []) throws {
|
||||
|
||||
let fileManager = FileManager.default
|
||||
let extraFiles: [String] = [
|
||||
storeURL.path.appending("-wal"),
|
||||
storeURL.path.appending("-shm")
|
||||
]
|
||||
do {
|
||||
|
||||
let temporaryFile = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!)
|
||||
let trashURL = URL(fileURLWithPath: NSSearchPathForDirectoriesInDomains(.cachesDirectory, .userDomainMask, true).first!)
|
||||
.appendingPathComponent(Bundle.main.bundleIdentifier ?? "com.CoreStore.DataStack", isDirectory: true)
|
||||
.appendingPathComponent("trash", isDirectory: true)
|
||||
.appendingPathComponent(UUID().uuidString, isDirectory: false)
|
||||
try fileManager.createDirectory(
|
||||
at: temporaryFile.deletingLastPathComponent(),
|
||||
at: trashURL,
|
||||
withIntermediateDirectories: true,
|
||||
attributes: nil
|
||||
)
|
||||
try fileManager.moveItem(at: fileURL, to: temporaryFile)
|
||||
|
||||
let temporaryFileURL = trashURL.appendingPathComponent(UUID().uuidString, isDirectory: false)
|
||||
try fileManager.moveItem(at: storeURL, to: temporaryFileURL)
|
||||
|
||||
let extraTemporaryFiles = extraFiles.map { (extraFile) -> String in
|
||||
|
||||
let temporaryFile = trashURL.appendingPathComponent(UUID().uuidString, isDirectory: false).path
|
||||
if let _ = try? fileManager.moveItem(atPath: extraFile, toPath: temporaryFile) {
|
||||
|
||||
return temporaryFile
|
||||
}
|
||||
return extraFile
|
||||
}
|
||||
DispatchQueue.global(qos: .background).async {
|
||||
|
||||
_ = try? fileManager.removeItem(at: temporaryFile)
|
||||
_ = try? fileManager.removeItem(at: temporaryFileURL)
|
||||
extraTemporaryFiles.forEach({ _ = try? fileManager.removeItem(atPath: $0) })
|
||||
}
|
||||
}
|
||||
catch {
|
||||
|
||||
try fileManager.removeItem(at: fileURL)
|
||||
try fileManager.removeItem(at: storeURL)
|
||||
extraFiles.forEach({ _ = try? fileManager.removeItem(atPath: $0) })
|
||||
}
|
||||
}
|
||||
|
||||
let fileURL = self.fileURL
|
||||
try autoreleasepool {
|
||||
|
||||
if let soureModel = soureModelHint ?? NSManagedObjectModel.mergedModel(from: nil, forStoreMetadata: metadata) {
|
||||
|
||||
let journalUpdatingCoordinator = NSPersistentStoreCoordinator(managedObjectModel: soureModel)
|
||||
let store = try journalUpdatingCoordinator.addPersistentStore(
|
||||
ofType: type(of: self).storeType,
|
||||
configurationName: self.configuration,
|
||||
at: fileURL,
|
||||
options: [NSSQLitePragmasOption: ["journal_mode": "DELETE"]]
|
||||
)
|
||||
try journalUpdatingCoordinator.remove(store)
|
||||
}
|
||||
try deleteFiles(storeURL: fileURL)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -158,7 +158,7 @@ public protocol LocalStorage: StorageInterface {
|
||||
/**
|
||||
Called by the `DataStack` to perform actual deletion of the store file from disk. **Do not call directly!** The `sourceModel` argument is a hint for the existing store's model version. Implementers can use the `sourceModel` to perform necessary store operations. (SQLite stores for example, can convert WAL journaling mode to DELETE before deleting)
|
||||
*/
|
||||
func eraseStorageAndWait(soureModel: NSManagedObjectModel) throws
|
||||
func eraseStorageAndWait(metadata: [String: Any], soureModelHint: NSManagedObjectModel?) throws
|
||||
}
|
||||
|
||||
internal extension LocalStorage {
|
||||
|
||||
Reference in New Issue
Block a user