From 3c514830d9f0db950b5617ba4f9064663cf69316 Mon Sep 17 00:00:00 2001 From: John Rommel Estropia Date: Fri, 4 Mar 2016 07:51:35 +0900 Subject: [PATCH] WIP: SQLiteStore implementation --- CoreStore.xcodeproj/project.pbxproj | 10 ++ CoreStore/Setting Up/CoreStore+Setup.swift | 21 ++- CoreStore/Setting Up/DataStack.swift | 80 +++++---- .../PersistentStores/InMemoryStore.swift | 38 ++++- .../PersistentStores/LegacySQLiteStore.swift | 90 ++++++++++ .../PersistentStores/SQLiteStore.swift | 159 ++++++++++++++---- .../PersistentStores/StorageInterface.swift | 39 +---- 7 files changed, 331 insertions(+), 106 deletions(-) create mode 100644 CoreStore/Setting Up/PersistentStores/LegacySQLiteStore.swift diff --git a/CoreStore.xcodeproj/project.pbxproj b/CoreStore.xcodeproj/project.pbxproj index 432a53a..3a2b6fb 100644 --- a/CoreStore.xcodeproj/project.pbxproj +++ b/CoreStore.xcodeproj/project.pbxproj @@ -219,6 +219,10 @@ B5D372841A39CD6900F583D9 /* Model.xcdatamodeld in Sources */ = {isa = PBXBuildFile; fileRef = B5D372821A39CD6900F583D9 /* Model.xcdatamodeld */; }; B5D372861A39CDDB00F583D9 /* TestEntity1.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D372851A39CDDB00F583D9 /* TestEntity1.swift */; }; B5D39A0219FD00C9000E91BB /* Foundation.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B5D39A0119FD00C9000E91BB /* Foundation.framework */; }; + B5D3F6451C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D3F6441C887C0A00C7492A /* LegacySQLiteStore.swift */; }; + B5D3F6461C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D3F6441C887C0A00C7492A /* LegacySQLiteStore.swift */; }; + B5D3F6471C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D3F6441C887C0A00C7492A /* LegacySQLiteStore.swift */; }; + B5D3F6481C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D3F6441C887C0A00C7492A /* LegacySQLiteStore.swift */; }; B5D5E0CF1A4D6AAB006468AF /* TestEntity2.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */; }; B5E834B91B76311F001D3D50 /* BaseDataTransaction+Importing.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */; }; B5E834BB1B7691F3001D3D50 /* Functions.swift in Sources */ = {isa = PBXBuildFile; fileRef = B5E834BA1B7691F3001D3D50 /* Functions.swift */; }; @@ -344,6 +348,7 @@ B5D372831A39CD6900F583D9 /* Model.xcdatamodel */ = {isa = PBXFileReference; lastKnownFileType = wrapper.xcdatamodel; path = Model.xcdatamodel; sourceTree = ""; }; B5D372851A39CDDB00F583D9 /* TestEntity1.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity1.swift; sourceTree = ""; }; B5D39A0119FD00C9000E91BB /* Foundation.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Foundation.framework; path = System/Library/Frameworks/Foundation.framework; sourceTree = SDKROOT; }; + B5D3F6441C887C0A00C7492A /* LegacySQLiteStore.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LegacySQLiteStore.swift; sourceTree = ""; }; B5D5E0CE1A4D6AAB006468AF /* TestEntity2.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TestEntity2.swift; sourceTree = ""; }; B5D9C8F61B160ED200E64F0E /* CoreStore.podspec */ = {isa = PBXFileReference; explicitFileType = text.script.ruby; path = CoreStore.podspec; sourceTree = SOURCE_ROOT; }; B5E834B81B76311F001D3D50 /* BaseDataTransaction+Importing.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "BaseDataTransaction+Importing.swift"; sourceTree = ""; }; @@ -709,6 +714,7 @@ B5FE4DA11C8481E100FA6A91 /* StorageInterface.swift */, B5FE4DA61C84FB4400FA6A91 /* InMemoryStore.swift */, B5FE4DAB1C85D44E00FA6A91 /* SQLiteStore.swift */, + B5D3F6441C887C0A00C7492A /* LegacySQLiteStore.swift */, ); path = PersistentStores; sourceTree = ""; @@ -997,6 +1003,7 @@ B504D0D61B02362500B2BBB1 /* CoreStore+Setup.swift in Sources */, B5D1E22C19FA9FBC003B2874 /* NSError+CoreStore.swift in Sources */, B5E84F131AFF847B0064E85B /* Where.swift in Sources */, + B5D3F6451C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */, B54A6A551BA15F2A007870FD /* FetchedResultsControllerDelegate.swift in Sources */, B5A261211B64BFDB006EB6D3 /* MigrationType.swift in Sources */, B5E84F141AFF847B0064E85B /* DataStack+Querying.swift in Sources */, @@ -1085,6 +1092,7 @@ 82BA18A51C4BBD2200A0916E /* CoreStore+Setup.swift in Sources */, 82BA18BD1C4BBD4A00A0916E /* GroupBy.swift in Sources */, B5C976E41C6C9F9A00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */, + B5D3F6461C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */, 82BA18B31C4BBD3900A0916E /* ImportableUniqueObject.swift in Sources */, 82BA18A11C4BBD1D00A0916E /* CoreStore.swift in Sources */, 82BA18CF1C4BBD7100A0916E /* Functions.swift in Sources */, @@ -1158,6 +1166,7 @@ B52DD1AB1BE1F93900949AFE /* From.swift in Sources */, B52DD1BF1BE1F94600949AFE /* AssociatedObjects.swift in Sources */, B52DD1A11BE1F92C00949AFE /* DataStack+Transaction.swift in Sources */, + B5D3F6481C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */, B52DD19E1BE1F92C00949AFE /* AsynchronousDataTransaction.swift in Sources */, B52DD1981BE1F92500949AFE /* CoreStore+Setup.swift in Sources */, B52DD1941BE1F92500949AFE /* CoreStore.swift in Sources */, @@ -1234,6 +1243,7 @@ B56321891BD65216006C9394 /* AsynchronousDataTransaction.swift in Sources */, B56321831BD65216006C9394 /* CoreStore+Setup.swift in Sources */, B5C976E51C6C9F9B00B1AF90 /* UnsafeDataTransaction+Observing.swift in Sources */, + B5D3F6471C887C0A00C7492A /* LegacySQLiteStore.swift in Sources */, B563217F1BD65216006C9394 /* CoreStore.swift in Sources */, B56321911BD65216006C9394 /* BaseDataTransaction+Importing.swift in Sources */, B56321941BD65216006C9394 /* CoreStore+Querying.swift in Sources */, diff --git a/CoreStore/Setting Up/CoreStore+Setup.swift b/CoreStore/Setting Up/CoreStore+Setup.swift index 210846e..c49d7a3 100644 --- a/CoreStore/Setting Up/CoreStore+Setup.swift +++ b/CoreStore/Setting Up/CoreStore+Setup.swift @@ -83,13 +83,21 @@ public extension CoreStore { // MARK: Deprecated + /** + Deprecated. Use `addStorageAndWait(_:)` by passing a `InMemoryStore` instance. + */ @available(*, deprecated=2.0.0, obsoleted=2.0.0, message="Use addStorageAndWait(_:) by passing an InMemoryStore instance.") public static func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore { return try self.defaultStack.addInMemoryStoreAndWait(configuration: configuration) } - @available(*, deprecated=2.0.0, message="Use addStorageAndWait(_:) by passing an SQLiteStore instance. Note that the previous default directory for the SQLite file was in the \"Application Support\" directory (or the \"Caches\" directory on tvOS), but the new addStorageAndWait(_:configuration:) method's default directory is now in the \"Application Support/\" directory (or the \"Caches/\" directory on tvOS)") + /** + Deprecated. Use `addStorageAndWait(_:)` by passing a `LegacySQLiteStore` instance. + + - Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`. + */ + @available(*, deprecated=2.0.0, message="Use addStorageAndWait(_:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.") public static func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore { return try self.defaultStack.addSQLiteStoreAndWait( @@ -99,10 +107,15 @@ public extension CoreStore { ) } - @available(*, deprecated=2.0.0, message="Use addStorageAndWait(_:) by passing an SQLiteStore instance. Note that the previous default URL for the SQLite file was in the \"Application Support/.sqlite\" directory (or the \"Caches/.sqlite\" directory on tvOS), but the new addStorageAndWait(_:configuration:) method's default directory is now in the \"Application Support//.sqlite\" directory (or the \"Caches//.sqlite\" directory on tvOS)") - public static func addSQLiteStoreAndWait(fileURL fileURL: NSURL = DataStack.DeprecatedDefaults.defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore { + /** + Deprecated. Use `addStorageAndWait(_:)` by passing a `LegacySQLiteStore` instance. + + - Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`. + */ + @available(*, deprecated=2.0.0, message="Use addStorageAndWait(_:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.") + public static func addSQLiteStoreAndWait(fileURL fileURL: NSURL = LegacySQLiteStore.legacyDefaultFileURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore { - return try self.addSQLiteStoreAndWait( + return try self.defaultStack.addSQLiteStoreAndWait( fileURL: fileURL, configuration: configuration, resetStoreOnModelMismatch: resetStoreOnModelMismatch diff --git a/CoreStore/Setting Up/DataStack.swift b/CoreStore/Setting Up/DataStack.swift index d4c697d..9d4f67c 100644 --- a/CoreStore/Setting Up/DataStack.swift +++ b/CoreStore/Setting Up/DataStack.swift @@ -122,8 +122,33 @@ public final class DataStack { CoreStore.assert( store.internalStore == nil, - "The specified store was already added to the data stack: \(store)" + "The specified \"\(typeName(store))\" was already added to the data stack: \(store)" ) + CoreStore.assert( + T.validateStoreURL(store.storeURL), + "The specified store URL for the \"\(typeName(store))\" is invalid: \"\(store.storeURL)\"" + ) + +// TODO: check +// if let store = coordinator.persistentStoreForURL(fileURL) { +// +// guard store.type == NSSQLiteStoreType +// && store.configurationName == (configuration ?? Into.defaultConfigurationName) else { +// +// let error = NSError(coreStoreErrorCode: .DifferentPersistentStoreExistsAtURL) +// CoreStore.handleError( +// error, +// "Failed to add SQLite \(typeName(NSPersistentStore)) at \"\(fileURL)\" because a different \(typeName(NSPersistentStore)) at that URL already exists." +// ) +// throw error +// } +// +// GCDQueue.Main.async { +// +// completion(PersistentStoreResult(store)) +// } +// return nil +// } do { @@ -145,6 +170,8 @@ public final class DataStack { // MARK: Internal + internal static let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData" + internal let coordinator: NSPersistentStoreCoordinator internal let rootSavingContext: NSManagedObjectContext internal let mainContext: NSManagedObjectContext @@ -243,9 +270,7 @@ public final class DataStack { } - // MARK: Private] - - private static let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData" + // MARK: Private private var configurationStoreMapping = [String: NSPersistentStore]() private var entityConfigurationsMapping = [String: Set]() @@ -259,24 +284,9 @@ public final class DataStack { // MARK: Deprecated - internal enum DeprecatedDefaults { - - #if os(tvOS) - internal static let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory - #else - internal static let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory - #endif - - internal static let defaultDirectory = NSFileManager.defaultManager().URLsForDirectory( - DeprecatedDefaults.systemDirectorySearchPath, - inDomains: .UserDomainMask - ).first! - - internal static let defaultSQLiteStoreURL = DeprecatedDefaults.defaultDirectory - .URLByAppendingPathComponent(DataStack.applicationName, isDirectory: false) - .URLByAppendingPathExtension("sqlite") - } - + /** + Deprecated. Use `addStorageAndWait(_:)` by passing a `InMemoryStore` instance. + */ @available(*, deprecated=2.0.0, message="Use addStorageAndWait(_:) by passing an InMemoryStore instance.") public func addInMemoryStoreAndWait(configuration configuration: String? = nil) throws -> NSPersistentStore { @@ -284,15 +294,17 @@ public final class DataStack { return storage.internalStore! } - @available(*, deprecated=2.0.0, message="Use addStorageAndWait(_:) by passing an SQLiteStore instance. Note that the previous default directory for the SQLite file was in the \"Application Support\" directory (or the \"Caches\" directory on tvOS), but the new addStorageAndWait(_:configuration:) method's default directory is now in the \"Application Support/\" directory (or the \"Caches/\" directory on tvOS)") + /** + Deprecated. Use `addStorageAndWait(_:)` by passing a `LegacySQLiteStore` instance. + + - Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`. + */ + @available(*, deprecated=2.0.0, message="Use addStorageAndWait(_:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.") public func addSQLiteStoreAndWait(fileName fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore { let storage = try self.addStorageAndWait( - SQLiteStore( - fileURL: DeprecatedDefaults.defaultDirectory.URLByAppendingPathComponent( - fileName, - isDirectory: false - ), + LegacySQLiteStore( + fileName: fileName, configuration: configuration, resetStoreOnModelMismatch: resetStoreOnModelMismatch ) @@ -301,16 +313,16 @@ public final class DataStack { } /** - Deprecated. Use `addStorageAndWait(_:)` by passing an `SQLiteStore` instance. + Deprecated. Use `addStorageAndWait(_:)` by passing a `LegacySQLiteStore` instance. - - Warning: The previous default URL for the SQLite file was "Application Support/\.sqlite" ("Caches/\.sqlite" directory on tvOS), but the new `addStorageAndWait(_:)` method's default URL is now "Application Support/\/\.sqlite" ("Caches/\/\.sqlite" on tvOS) to better comply with per Apple's guidelines. + - Warning: The default SQLite file location for the `LegacySQLiteStore` and `SQLiteStore` are different. If the app was using this method prior to 2.0.0, make sure to use `LegacySQLiteStore`. */ - @available(*, deprecated=2.0.0, message="Use addStorageAndWait(_:) by passing an SQLiteStore instance. Warning: the previous default URL for the SQLite file was \"Application Support/.sqlite\" (\"Caches/.sqlite\" directory on tvOS), but the new addStorageAndWait(_:configuration:) method's default URL is now \"Application Support//.sqlite\" (\"Caches//.sqlite\" on tvOS) to better comply with per Apple's guidelines,") - public func addSQLiteStoreAndWait(fileURL fileURL: NSURL = DeprecatedDefaults.defaultSQLiteStoreURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore { + @available(*, deprecated=2.0.0, message="Use addStorageAndWait(_:) by passing a LegacySQLiteStore instance. Warning: The default SQLite file location for the LegacySQLiteStore and SQLiteStore are different. If the app was using this method prior to 2.0.0, make sure to use LegacySQLiteStore.") + public func addSQLiteStoreAndWait(fileURL fileURL: NSURL = LegacySQLiteStore.legacyDefaultFileURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) throws -> NSPersistentStore { let storage = try self.addStorageAndWait( - SQLiteStore( - fileURL: DeprecatedDefaults.defaultSQLiteStoreURL, + LegacySQLiteStore( + fileURL: fileURL, configuration: configuration, resetStoreOnModelMismatch: resetStoreOnModelMismatch ) diff --git a/CoreStore/Setting Up/PersistentStores/InMemoryStore.swift b/CoreStore/Setting Up/PersistentStores/InMemoryStore.swift index ff87b3f..effaaf3 100644 --- a/CoreStore/Setting Up/PersistentStores/InMemoryStore.swift +++ b/CoreStore/Setting Up/PersistentStores/InMemoryStore.swift @@ -48,11 +48,45 @@ public class InMemoryStore: StorageInterface, DefaultInitializableStore { public static let storeType = NSInMemoryStoreType + public static func validateStoreURL(storeURL: NSURL?) -> Bool { + + return storeURL == nil + } + public let storeURL: NSURL? = nil - public let configuration: String? - public let storeOptions: [String: AnyObject]? = nil public var internalStore: NSPersistentStore? + + public func addToPersistentStoreCoordinatorSynchronously(coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore { + + return try coordinator.addPersistentStoreSynchronously( + self.dynamicType.storeType, + configuration: self.configuration, + URL: self.storeURL, + options: self.storeOptions + ) + } + + public func addToPersistentStoreCoordinatorAsynchronously(coordinator: NSPersistentStoreCoordinator, mappingModelBundles: [NSBundle]?, completion: (NSPersistentStore) -> Void, failure: (NSError) -> Void) throws { + + coordinator.performBlock { + + do { + + let persistentStore = try coordinator.addPersistentStoreWithType( + self.dynamicType.storeType, + configuration: self.configuration, + URL: self.storeURL, + options: self.storeOptions + ) + completion(persistentStore) + } + catch { + + failure(error as NSError) + } + } + } } diff --git a/CoreStore/Setting Up/PersistentStores/LegacySQLiteStore.swift b/CoreStore/Setting Up/PersistentStores/LegacySQLiteStore.swift new file mode 100644 index 0000000..ad7658c --- /dev/null +++ b/CoreStore/Setting Up/PersistentStores/LegacySQLiteStore.swift @@ -0,0 +1,90 @@ +// +// LegacySQLiteStore.swift +// CoreStore +// +// Copyright © 2016 John Rommel Estropia +// +// Permission is hereby granted, free of charge, to any person obtaining a copy +// of this software and associated documentation files (the "Software"), to deal +// in the Software without restriction, including without limitation the rights +// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +// copies of the Software, and to permit persons to whom the Software is +// furnished to do so, subject to the following conditions: +// +// The above copyright notice and this permission notice shall be included in all +// copies or substantial portions of the Software. +// +// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +// SOFTWARE. +// + +import Foundation + + +// MARK: - LegacySQLiteStore + +public final class LegacySQLiteStore: SQLiteStore { + + public required init(fileURL: NSURL, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) { + + super.init( + fileURL: fileURL, + configuration: configuration, + resetStoreOnModelMismatch: resetStoreOnModelMismatch + ) + } + + /** + Initializes an SQLite store interface from the given SQLite file name. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist. + + - parameter fileName: the local filename for the SQLite persistent store in the "Application Support/" directory (or the "Caches/" directory on tvOS). 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 resetStoreOnModelMismatch: When the `SQLiteStore` is passed to the `DataStack`'s `addStorage()` methods, a true value tells the `DataStack` to delete the store on model mismatch; a false value lets exceptions be thrown on failure instead. Typically should only be set to true when debugging, or if the persistent store can be recreated easily. If not specified, defaults to false. + */ + public required init(fileName: String, configuration: String? = nil, resetStoreOnModelMismatch: Bool = false) { + + super.init( + fileURL: LegacySQLiteStore.legacyDefaultRootDirectory.URLByAppendingPathComponent( + fileName, + isDirectory: false + ), + configuration: configuration, + resetStoreOnModelMismatch: resetStoreOnModelMismatch + ) + } + + + // MARK: DefaultInitializableStore + + public required init() { + + super.init( + fileURL: LegacySQLiteStore.legacyDefaultFileURL, + configuration: nil, + resetStoreOnModelMismatch: false + ) + } + + + // MARK: Internal + + #if os(tvOS) + internal static let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory + #else + internal static let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory + #endif + + internal static let legacyDefaultRootDirectory = NSFileManager.defaultManager().URLsForDirectory( + LegacySQLiteStore.systemDirectorySearchPath, + inDomains: .UserDomainMask + ).first! + + internal static let legacyDefaultFileURL = LegacySQLiteStore.legacyDefaultRootDirectory + .URLByAppendingPathComponent(DataStack.applicationName, isDirectory: false) + .URLByAppendingPathExtension("sqlite") +} diff --git a/CoreStore/Setting Up/PersistentStores/SQLiteStore.swift b/CoreStore/Setting Up/PersistentStores/SQLiteStore.swift index 8a74e51..cbb47f9 100644 --- a/CoreStore/Setting Up/PersistentStores/SQLiteStore.swift +++ b/CoreStore/Setting Up/PersistentStores/SQLiteStore.swift @@ -30,33 +30,6 @@ import CoreData public class SQLiteStore: StorageInterface, DefaultInitializableStore { - public static let defaultRootDirectory: NSURL = { - - #if os(tvOS) - let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory - #else - let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory - #endif - - let defaultSystemDirectory = NSFileManager - .defaultManager() - .URLsForDirectory(systemDirectorySearchPath, inDomains: .UserDomainMask).first! - - return defaultSystemDirectory.URLByAppendingPathComponent( - NSBundle.mainBundle().bundleIdentifier ?? "com.CoreStore.DataStack", - isDirectory: true - ) - }() - - public static let defaultFileURL: NSURL = { - - let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData" - - return SQLiteStore.defaultRootDirectory - .URLByAppendingPathComponent(applicationName, isDirectory: false) - .URLByAppendingPathExtension("sqlite") - }() - /** Initializes an SQLite store interface from the given SQLite file URL. When this instance is passed to the `DataStack`'s `addStorage()` methods, a new SQLite file will be created if it does not exist. @@ -101,13 +74,17 @@ public class SQLiteStore: StorageInterface, DefaultInitializableStore { public static let storeType = NSSQLiteStoreType + public static func validateStoreURL(storeURL: NSURL?) -> Bool { + + return storeURL?.fileURL == true + } + public var storeURL: NSURL? { return self.fileURL } - + public let configuration: String? - public let storeOptions: [String: AnyObject]? = [NSSQLitePragmasOption: ["journal_mode": "WAL"]] public var internalStore: NSPersistentStore? @@ -144,9 +121,133 @@ public class SQLiteStore: StorageInterface, DefaultInitializableStore { } } + public func addToPersistentStoreCoordinatorAsynchronously(coordinator: NSPersistentStoreCoordinator, mappingModelBundles: [NSBundle]?, completion: (NSPersistentStore) -> Void, failure: (NSError) -> Void) throws { + + let fileManager = NSFileManager.defaultManager() + + do { + + let fileURL = self.fileURL + try fileManager.createDirectoryAtURL( + fileURL.URLByDeletingLastPathComponent!, + withIntermediateDirectories: true, + attributes: nil + ) + + let metadata = try NSPersistentStoreCoordinator.metadataForPersistentStoreOfType( + self.dynamicType.storeType, + URL: fileURL, + options: self.storeOptions + ) + + return self.upgradeSQLiteStoreIfNeeded( + fileURL: fileURL, + metadata: metadata, + configuration: configuration, + mappingModelBundles: mappingModelBundles, + completion: { (result) -> Void in + + if case .Failure(let error) = result { + + if resetStoreOnModelMismatch && error.isCoreDataMigrationError { + + fileManager.removeSQLiteStoreAtURL(fileURL) + do { + + let store = try self.addSQLiteStoreAndWait( + fileURL: fileURL, + configuration: configuration, + resetStoreOnModelMismatch: false + ) + + GCDQueue.Main.async { + + completion(PersistentStoreResult(store)) + } + } + catch { + + completion(PersistentStoreResult(error as NSError)) + } + return + } + + completion(PersistentStoreResult(error)) + return + } + + do { + + let store = try self.addSQLiteStoreAndWait( + fileURL: fileURL, + configuration: configuration, + resetStoreOnModelMismatch: false + ) + + completion(PersistentStoreResult(store)) + } + catch { + + completion(PersistentStoreResult(error as NSError)) + } + } + ) + } + catch let error as NSError + where error.code == NSFileReadNoSuchFileError && error.domain == NSCocoaErrorDomain { + + let store = try self.addSQLiteStoreAndWait( + fileURL: fileURL, + configuration: configuration, + resetStoreOnModelMismatch: false + ) + + GCDQueue.Main.async { + + completion(PersistentStoreResult(store)) + } + return nil + } + catch { + + CoreStore.handleError( + error as NSError, + "Failed to load SQLite \(typeName(NSPersistentStore)) metadata." + ) + throw error + } + } + // MARK: Internal + internal static let defaultRootDirectory: NSURL = { + + #if os(tvOS) + let systemDirectorySearchPath = NSSearchPathDirectory.CachesDirectory + #else + let systemDirectorySearchPath = NSSearchPathDirectory.ApplicationSupportDirectory + #endif + + let defaultSystemDirectory = NSFileManager + .defaultManager() + .URLsForDirectory(systemDirectorySearchPath, inDomains: .UserDomainMask).first! + + return defaultSystemDirectory.URLByAppendingPathComponent( + NSBundle.mainBundle().bundleIdentifier ?? "com.CoreStore.DataStack", + isDirectory: true + ) + }() + + internal static let defaultFileURL: NSURL = { + + let applicationName = (NSBundle.mainBundle().objectForInfoDictionaryKey("CFBundleName") as? String) ?? "CoreData" + + return SQLiteStore.defaultRootDirectory + .URLByAppendingPathComponent(applicationName, isDirectory: false) + .URLByAppendingPathExtension("sqlite") + }() + internal let fileURL: NSURL internal let resetStoreOnModelMismatch: Bool diff --git a/CoreStore/Setting Up/PersistentStores/StorageInterface.swift b/CoreStore/Setting Up/PersistentStores/StorageInterface.swift index a0313d3..2f78f16 100644 --- a/CoreStore/Setting Up/PersistentStores/StorageInterface.swift +++ b/CoreStore/Setting Up/PersistentStores/StorageInterface.swift @@ -31,52 +31,17 @@ import CoreData public protocol StorageInterface: class { static var storeType: String { get } + static func validateStoreURL(storeURL: NSURL?) -> Bool var storeURL: NSURL? { get } - var configuration: String? { get } - var storeOptions: [String: AnyObject]? { get } var internalStore: NSPersistentStore? { get set } func addToPersistentStoreCoordinatorSynchronously(coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore - func addToPersistentStoreCoordinatorAsynchronously(coordinator: NSPersistentStoreCoordinator, completion: (NSPersistentStore) -> Void, failure: (NSError) -> Void) throws -} - -public extension StorageInterface { - - public func addToPersistentStoreCoordinatorSynchronously(coordinator: NSPersistentStoreCoordinator) throws -> NSPersistentStore { - - return try coordinator.addPersistentStoreSynchronously( - self.dynamicType.storeType, - configuration: self.configuration, - URL: self.storeURL, - options: self.storeOptions - ) - } - - public func addToPersistentStoreCoordinatorAsynchronously(coordinator: NSPersistentStoreCoordinator, completion: (NSPersistentStore) -> Void, failure: (NSError) -> Void) throws { - - coordinator.performBlock { - - do { - - let persistentStore = try coordinator.addPersistentStoreWithType( - self.dynamicType.storeType, - configuration: self.configuration, - URL: self.storeURL, - options: self.storeOptions - ) - completion(persistentStore) - } - catch { - - failure(error as NSError) - } - } - } + func addToPersistentStoreCoordinatorAsynchronously(coordinator: NSPersistentStoreCoordinator, mappingModelBundles: [NSBundle]?, completion: (NSPersistentStore) -> Void, failure: (NSError) -> Void) throws }