diff --git a/Source/AppleStorage/Implement/CoreDataArrayStorage.swift b/Source/AppleStorage/Implement/CoreDataArrayStorage.swift index 7ceafd7..25bc00f 100644 --- a/Source/AppleStorage/Implement/CoreDataArrayStorage.swift +++ b/Source/AppleStorage/Implement/CoreDataArrayStorage.swift @@ -12,9 +12,11 @@ import SabySafe import SabySize import SabyJSON +private let STORAGE_VERSION = "Version1" + public final class CoreDataArrayStorage: ArrayStorage { typealias Context = NSManagedObjectContext - typealias Item = SabyCoreDataArrayStorageItem + typealias Item = SabyCoreDataArrayStorageItemVersion1 let entity: NSEntityDescription @@ -24,7 +26,7 @@ public final class CoreDataArrayStorage: Array let encoder = JSONEncoder.acceptingNonConfirmingFloat() let decoder = JSONDecoder.acceptingNonConfirmingFloat() - public init(directoryURL: URL, fileName: String, migration: @escaping () -> Promise) { + public init(directoryURL: URL, storageName: String, migration: @escaping () -> Promise) { let schema = SabyCoreDataArrayStorageSchema() self.entity = schema.entity @@ -32,7 +34,7 @@ public final class CoreDataArrayStorage: Array self.contextLoad = { Context.load( directoryURL: directoryURL, - fileName: fileName, + storageName: storageName, migration: migration, model: schema.model ) @@ -50,7 +52,7 @@ extension CoreDataArrayStorage { let request = self.createAnyRequest(key: key) try context.executeDelete(request) - let item = SabyCoreDataArrayStorageItem( + let item = SabyCoreDataArrayStorageItemVersion1( entity: self.entity, insertInto: context ) @@ -70,7 +72,7 @@ extension CoreDataArrayStorage { try context.executeDelete(request) for value in values { - let item = SabyCoreDataArrayStorageItem( + let item = SabyCoreDataArrayStorageItemVersion1( entity: self.entity, insertInto: context ) @@ -195,7 +197,7 @@ extension CoreDataArrayStorage { } fileprivate func createSizeRequest() -> NSFetchRequest { - let byteExpression = NSExpression(forKeyPath: \SabyCoreDataArrayStorageItem.byte) + let byteExpression = NSExpression(forKeyPath: \SabyCoreDataArrayStorageItemVersion1.byte) let sumExpression = NSExpression(forFunction: "sum:", arguments: [byteExpression]) let sumDescription = NSExpressionDescription() sumDescription.expression = sumExpression @@ -335,7 +337,7 @@ extension NSManagedObjectContext { extension NSManagedObjectContext { static func load( directoryURL: URL, - fileName: String, + storageName: String, migration: @escaping () -> Promise, model: NSManagedObjectModel ) -> Promise { @@ -353,10 +355,10 @@ extension NSManagedObjectContext { ) } - let url = directoryURL.appendingPathComponent(fileName) + let url = directoryURL.appendingPathComponent(storageName).appendingPathExtension(STORAGE_VERSION) let container = NSPersistentContainer( - name: fileName, + name: storageName, managedObjectModel: model ) let storeDescription = NSPersistentStoreDescription(url: url) @@ -380,8 +382,9 @@ public enum CoreDataArrayStorageError: Error { case requestResultNotFound } -@objc(SabyCoreDataArrayStorageItem) -final class SabyCoreDataArrayStorageItem: NSManagedObject { +// Must not be modified. Write ItemVersion2 and write migration logic instead. +@objc(SabyCoreDataArrayStorageItemVersion1) +final class SabyCoreDataArrayStorageItemVersion1: NSManagedObject { @NSManaged var key: UUID @NSManaged var data: Data @NSManaged var date: Date @@ -426,8 +429,8 @@ final class SabyCoreDataArrayStorageSchema { } let itemEntity = NSEntityDescription() - itemEntity.name = String(describing: SabyCoreDataArrayStorageItem.self) - itemEntity.managedObjectClassName = String(describing: SabyCoreDataArrayStorageItem.self) + itemEntity.name = String(describing: SabyCoreDataArrayStorageItemVersion1.self) + itemEntity.managedObjectClassName = String(describing: SabyCoreDataArrayStorageItemVersion1.self) itemEntity.properties = [ keyAttribute, dataAttribute, diff --git a/Source/AppleStorage/Implement/FileArrayStorage.swift b/Source/AppleStorage/Implement/FileArrayStorage.swift index f9c28ba..895f4bc 100644 --- a/Source/AppleStorage/Implement/FileArrayStorage.swift +++ b/Source/AppleStorage/Implement/FileArrayStorage.swift @@ -10,9 +10,11 @@ import SabyConcurrency import SabySize import SabyJSON +private let STORAGE_VERSION = "Version1" + public final class FileArrayStorage: ArrayStorage { typealias Context = FileArrayStorageContext - typealias Item = FileArrayStorageItem + typealias Item = FileArrayStorageItemVersion1 let fileLock = Lock() @@ -21,11 +23,11 @@ public final class FileArrayStorage: ArrayStor let encoder = JSONEncoder.acceptingNonConfirmingFloat() - public init(directoryURL: URL, fileName: String, migration: @escaping () -> Promise) { + public init(directoryURL: URL, storageName: String, migration: @escaping () -> Promise) { self.contextLoad = { Context.load( directoryURL: directoryURL, - fileName: fileName, + storageName: storageName, migration: migration ) } @@ -191,11 +193,11 @@ extension FileArrayStorage { struct FileArrayStorageContext { let url: URL - let items: Atomic<[FileArrayStorageItem]> + let items: Atomic<[FileArrayStorageItemVersion1]> private init( url: URL, - items: Atomic<[FileArrayStorageItem]> + items: Atomic<[FileArrayStorageItemVersion1]> ) { self.url = url self.items = items @@ -203,7 +205,7 @@ struct FileArrayStorageContext { static func load( directoryURL: URL, - fileName: String, + storageName: String, migration: @escaping () -> Promise ) -> Promise { migration().then { @@ -221,7 +223,7 @@ struct FileArrayStorageContext { ) } - let url = directoryURL.appendingPathComponent(fileName) + let url = directoryURL.appendingPathComponent(storageName).appendingPathExtension(STORAGE_VERSION) if !fileManager.fileExists(atPath: url.path) { return FileArrayStorageContext( url: url, @@ -232,7 +234,7 @@ struct FileArrayStorageContext { guard let data = try? Data(contentsOf: url), let items = try? decoder.decode( - [FileArrayStorageItem].self, + [FileArrayStorageItemVersion1].self, from: data ) else { @@ -254,7 +256,8 @@ public enum FileArrayStorageError: Error { case libraryDirectoryNotFound } -struct FileArrayStorageItem: Codable { +// Must not be modified. Write ItemVersion2 and write migration logic instead. +struct FileArrayStorageItemVersion1: Codable { let key: UUID let value: Value let date: Date diff --git a/Source/AppleStorage/Implement/FileDictionaryStorage.swift b/Source/AppleStorage/Implement/FileDictionaryStorage.swift index f4e2a69..cb170ce 100644 --- a/Source/AppleStorage/Implement/FileDictionaryStorage.swift +++ b/Source/AppleStorage/Implement/FileDictionaryStorage.swift @@ -9,6 +9,8 @@ import Foundation import SabyConcurrency import SabyJSON +private let STORAGE_VERSION = "Version1" + public final class FileDictionaryStorage< Key: Hashable & Codable, Value: Codable @@ -22,11 +24,11 @@ public final class FileDictionaryStorage< let encoder = JSONEncoder.acceptingNonConfirmingFloat() - public init(directoryURL: URL, fileName: String, migration: @escaping () -> Promise) { + public init(directoryURL: URL, storageName: String, migration: @escaping () -> Promise) { self.contextLoad = { Context.load( directoryURL: directoryURL, - fileName: fileName, + storageName: storageName, migration: migration ) } @@ -107,16 +109,16 @@ extension FileDictionaryStorage { struct FileDictionaryStorageContext { let url: URL - let values: Atomic<[Key: Value]> + let values: Atomic> - private init(url: URL, values: Atomic<[Key: Value]>) { + private init(url: URL, values: Atomic>) { self.url = url self.values = values } static func load( directoryURL: URL, - fileName: String, + storageName: String, migration: @escaping () -> Promise ) -> Promise { migration().then { @@ -134,7 +136,7 @@ struct FileDictionaryStorageContext { ) } - let url = directoryURL.appendingPathComponent(fileName) + let url = directoryURL.appendingPathComponent(storageName).appendingPathExtension(STORAGE_VERSION) if !fileManager.fileExists(atPath: url.path) { return FileDictionaryStorageContext( url: url, @@ -145,7 +147,7 @@ struct FileDictionaryStorageContext { guard let data = try? Data(contentsOf: url), let values = try? decoder.decode( - [Key: Value].self, + FileDictionaryStorageItemVersion1.self, from: data ) else { @@ -162,3 +164,6 @@ struct FileDictionaryStorageContext { } } } + +// Must not be modified. Write ItemVersion2 and write migration logic instead. +typealias FileDictionaryStorageItemVersion1 = [Key: Value] diff --git a/Source/AppleStorage/Implement/FileValueStorage.swift b/Source/AppleStorage/Implement/FileValueStorage.swift index d73b335..23f4ac3 100644 --- a/Source/AppleStorage/Implement/FileValueStorage.swift +++ b/Source/AppleStorage/Implement/FileValueStorage.swift @@ -10,6 +10,8 @@ import SabySafe import SabyConcurrency import SabyJSON +private let STORAGE_VERSION = "Version1" + public final class FileValueStorage: ValueStorage { typealias Context = FileValueStorageContext @@ -20,11 +22,11 @@ public final class FileValueStorage: ValueStorage { let encoder = JSONEncoder.acceptingNonConfirmingFloat() - public init(directoryURL: URL, fileName: String, migration: @escaping () -> Promise) { + public init(directoryURL: URL, storageName: String, migration: @escaping () -> Promise) { self.contextLoad = { Context.load( directoryURL: directoryURL, - fileName: fileName, + storageName: storageName, migration: migration ) } @@ -91,16 +93,16 @@ extension FileValueStorage { struct FileValueStorageContext { let url: URL - let value: Atomic + let value: Atomic?> - private init(url: URL, value: Atomic) { + private init(url: URL, value: Atomic?>) { self.url = url self.value = value } static func load( directoryURL: URL, - fileName: String, + storageName: String, migration: @escaping () -> Promise ) -> Promise { migration().then { @@ -118,7 +120,7 @@ struct FileValueStorageContext { ) } - let url = directoryURL.appendingPathComponent(fileName) + let url = directoryURL.appendingPathComponent(storageName).appendingPathExtension(STORAGE_VERSION) if !fileManager.fileExists(atPath: url.path) { return FileValueStorageContext( url: url, @@ -129,7 +131,7 @@ struct FileValueStorageContext { guard let data = try? Data(contentsOf: url), let value = try? decoder.decode( - Value?.self, + FileValueStorageItemVersion1?.self, from: data ) else { @@ -146,3 +148,6 @@ struct FileValueStorageContext { } } } + +// Must not be modified. Write ItemVersion2 and write migration logic instead. +typealias FileValueStorageItemVersion1 = Value diff --git a/Source/AppleStorage/Storage.swift b/Source/AppleStorage/Storage.swift index 9d55eff..65b53d1 100644 --- a/Source/AppleStorage/Storage.swift +++ b/Source/AppleStorage/Storage.swift @@ -9,17 +9,17 @@ import Foundation import SabyConcurrency public protocol Storage { - /// ``init(directoryURL:fileName:migration:)`` + /// ``init(directoryURL:storageName:migration:)`` /// execute migration and then create or load preference from - /// `{Directory url}/{File name}` path. - init(directoryURL: URL, fileName: String, migration: @escaping () -> Promise) + /// `{Directory url}/{Storage name}/{Version name}` path. + init(directoryURL: URL, storageName: String, migration: @escaping () -> Promise) } extension Storage { - /// ``init(directoryName:fileName:)`` create or load storage from - /// `{Directory url}/{File name}` path. - public init(directoryURL: URL, fileName: String) { - self.init(directoryURL: directoryURL, fileName: fileName, migration: { .resolved(()) }) + /// ``init(directoryName:storageName:)`` create or load storage from + /// `{Directory url}/{Storage name}/{Version name}` path. + public init(directoryURL: URL, storageName: String) { + self.init(directoryURL: directoryURL, storageName: storageName, migration: { .resolved(()) }) } } diff --git a/Test/AppleStorage/Implement/CoreDataArrayStorageTest.swift b/Test/AppleStorage/Implement/CoreDataArrayStorageTest.swift index 9f9374c..7e74738 100644 --- a/Test/AppleStorage/Implement/CoreDataArrayStorageTest.swift +++ b/Test/AppleStorage/Implement/CoreDataArrayStorageTest.swift @@ -20,7 +20,7 @@ class CoreDataArrayStorageTest: XCTestCase { var encoder: JSONEncoder! override func setUpWithError() throws { - storage = CoreDataArrayStorage(directoryURL: FileManager.default.temporaryDirectory, fileName: "\(UUID())") + storage = CoreDataArrayStorage(directoryURL: FileManager.default.temporaryDirectory, storageName: "\(UUID())") encoder = JSONEncoder() } diff --git a/Test/AppleStorage/Implement/FileArrayStorageTest.swift b/Test/AppleStorage/Implement/FileArrayStorageTest.swift index 1c624bd..03c4639 100644 --- a/Test/AppleStorage/Implement/FileArrayStorageTest.swift +++ b/Test/AppleStorage/Implement/FileArrayStorageTest.swift @@ -17,7 +17,7 @@ final class FileArrayStorageTest: XCTestCase { fileprivate var storage: FileArrayStorage! fileprivate var encoder: JSONEncoder! fileprivate let directoryURL = FileManager.default.temporaryDirectory - fileprivate var fileName: String! + fileprivate var storageName: String! private struct TestItemGroup { private static let testCount = 100 @@ -37,16 +37,16 @@ final class FileArrayStorageTest: XCTestCase { } override func setUpWithError() throws { - fileName = UUID().uuidString + storageName = UUID().uuidString storage = FileArrayStorage( directoryURL: directoryURL, - fileName: fileName + storageName: storageName ) encoder = JSONEncoder() } override func tearDownWithError() throws { - let fileURL = directoryURL.appendingPathComponent(fileName) + let fileURL = directoryURL.appendingPathComponent(storageName) if FileManager.default.fileExists(atPath: fileURL.absoluteString) { try FileManager.default.removeItem(at: fileURL) diff --git a/Test/AppleStorage/Implement/FileDictionaryStorageTest.swift b/Test/AppleStorage/Implement/FileDictionaryStorageTest.swift index 6331651..d11381d 100644 --- a/Test/AppleStorage/Implement/FileDictionaryStorageTest.swift +++ b/Test/AppleStorage/Implement/FileDictionaryStorageTest.swift @@ -17,7 +17,7 @@ final class FileDictionaryStorageTest: XCTestCase { fileprivate let testCount = 500 fileprivate var storage: FileDictionaryStorage! fileprivate let directoryURL = FileManager.default.temporaryDirectory - fileprivate var fileName: String! + fileprivate var storageName: String! fileprivate var testObjects: [(String, DummyItem)] { var result: [(String, DummyItem)] = [] @@ -29,15 +29,15 @@ final class FileDictionaryStorageTest: XCTestCase { } override func setUpWithError() throws { - fileName = UUID().uuidString + storageName = UUID().uuidString storage = FileDictionaryStorage( directoryURL: directoryURL, - fileName: fileName + storageName: storageName ) } override func tearDownWithError() throws { - let fileURL = directoryURL.appendingPathComponent(fileName) + let fileURL = directoryURL.appendingPathComponent(storageName) if FileManager.default.fileExists(atPath: fileURL.absoluteString) { try FileManager.default.removeItem(at: fileURL) diff --git a/Test/AppleStorage/Implement/FileValueStorageTest.swift b/Test/AppleStorage/Implement/FileValueStorageTest.swift index 26614e5..a2296bb 100644 --- a/Test/AppleStorage/Implement/FileValueStorageTest.swift +++ b/Test/AppleStorage/Implement/FileValueStorageTest.swift @@ -17,7 +17,7 @@ final class FileValueStorageTest: XCTestCase { fileprivate let testCount = 500 fileprivate var storage: FileValueStorage! fileprivate let directoryURL = FileManager.default.temporaryDirectory - fileprivate var fileName: String! + fileprivate var storageName: String! fileprivate var testObjects: [DummyItem] { var result: [DummyItem] = [] @@ -29,15 +29,15 @@ final class FileValueStorageTest: XCTestCase { } override func setUpWithError() throws { - fileName = UUID().uuidString + storageName = UUID().uuidString storage = FileValueStorage( directoryURL: directoryURL, - fileName: fileName + storageName: storageName ) } override func tearDownWithError() throws { - let fileURL = directoryURL.appendingPathComponent(fileName) + let fileURL = directoryURL.appendingPathComponent(storageName) if FileManager.default.fileExists(atPath: fileURL.absoluteString) { try FileManager.default.removeItem(at: fileURL)