OSDN Git Service

CoreDataのエンティティ名を文字列でアクセスしないようにした
[kcd/KCD.git] / KCD / CoreDataCore.swift
1 //
2 //  CoreDataManager.swift
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2017/02/05.
6 //  Copyright © 2017年 Hori,Masaki. All rights reserved.
7 //
8
9 import Cocoa
10
11 enum CoreDataManagerType {
12     case reader
13     case editor
14 }
15
16 enum CoreDataError: Error {
17     case applicationDirectoryIsFile
18     case couldNotCreateModel
19     case couldNotCreateCoordinator(String)
20 }
21
22 struct CoreDataIntormation {
23     let modelName: String
24     let storeFileName: String
25     let storeOptions: Dictionary<AnyHashable, Any>
26     let storeType: String
27     let deleteAndRetry: Bool
28 }
29
30 struct CoreDataCore {
31     let info: CoreDataIntormation
32     let managedObjectModel: NSManagedObjectModel
33     let persistentStoreCoordinator: NSPersistentStoreCoordinator
34     let parentManagedObjectContext: NSManagedObjectContext
35     
36     init(_ info: CoreDataIntormation) {
37         self.info = info
38         let genaratee = try! MocGenerater.genarate(info)
39         self.managedObjectModel = genaratee.model
40         self.persistentStoreCoordinator = genaratee.coordinator
41         self.parentManagedObjectContext = genaratee.moc
42     }
43     
44     func editorManagedObjectContext() -> NSManagedObjectContext {
45         let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
46         moc.parent = parentManagedObjectContext
47         moc.undoManager = nil
48         return moc
49     }
50 }
51
52 struct Entity {
53     let name: String
54 }
55
56 protocol CoreDataProvider {
57     init(type: CoreDataManagerType)
58     var core: CoreDataCore { get }
59     var managedObjectContext: NSManagedObjectContext { get }
60     func saveActionCore()
61 }
62
63 protocol CoreDataManager {
64     associatedtype InstanceType = Self
65     
66     static var `default`: InstanceType { get }
67     static func oneTimeEditor() -> InstanceType
68     
69     func removeDatabaseFile()
70 }
71
72 protocol CoreDataAccessor: CoreDataProvider {
73     func insertNewObject(for entity: Entity) -> NSManagedObject
74     func delete(_ object: NSManagedObject)
75     func object(with objectId: NSManagedObjectID) -> NSManagedObject
76     func objects(with entity: Entity, sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) throws -> [NSManagedObject]
77 }
78
79 private class CoreDataRemover {
80     class func remove(name: String) {
81         ["", "-wal", "-shm"]
82             .map { name + $0 }
83             .map { ApplicationDirecrories.support.appendingPathComponent($0) }
84             .forEach { removeDatabaseFileAtURL(url: $0) }
85     }
86     private class func removeDatabaseFileAtURL(url: URL) {
87         do {
88             try FileManager.default.removeItem(at: url)
89         }
90         catch {
91             print("Could not remove file for URL (\(url))");
92         }
93     }
94 }
95
96 private class MocGenerater {
97     class func genarate(_ info: CoreDataIntormation) throws -> (model: NSManagedObjectModel, coordinator: NSPersistentStoreCoordinator, moc: NSManagedObjectContext) {
98         do {
99             let model = try createManagedObjectModel(info)
100             let coordinator = try createPersistentStoreCoordinator(info, model)
101             let moc = createManagedObjectContext(coordinator)
102             return (model: model, coordinator: coordinator, moc: moc)
103         }
104         catch {
105             throw error
106         }
107     }
108     
109     private class func createManagedObjectModel(_ info: CoreDataIntormation) throws -> NSManagedObjectModel {
110         let modelURL = Bundle.main.url(forResource: info.modelName, withExtension: "momd")!
111         guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
112             throw CoreDataError.couldNotCreateModel
113         }
114         return model
115     }
116     
117     private class func createPersistentStoreCoordinator(_ info: CoreDataIntormation, _ model: NSManagedObjectModel) throws -> NSPersistentStoreCoordinator {
118         var failError: NSError? = nil
119         var shouldFail = false
120         var failureReason = "There was an error creating or loading the application's saved data."
121         
122         do {
123             let p = try ApplicationDirecrories.support.resourceValues(forKeys: [.isDirectoryKey])
124             if !p.isDirectory! {
125                 failureReason = "Expected a folder to store application data, found a file \(ApplicationDirecrories.support.path)."
126                 shouldFail = true
127             }
128         }
129         catch {
130             let nserror = error as NSError
131             if nserror.code == NSFileReadNoSuchFileError {
132                 do {
133                     try FileManager.default.createDirectory(at: ApplicationDirecrories.support, withIntermediateDirectories: false, attributes: nil)
134                 } catch {
135                     failError = nserror
136                 }
137             } else {
138                 failError = nserror
139             }
140         }
141         
142         var coordinator: NSPersistentStoreCoordinator? = nil
143         if failError == nil {
144             coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
145             let url = ApplicationDirecrories.support.appendingPathComponent(info.storeFileName)
146             do {
147                 try coordinator!.addPersistentStore(ofType: info.storeType, configurationName: nil, at: url, options: info.storeOptions)
148             } catch {
149                 failError = error as NSError
150                 
151                 // Data Modelが更新されていたらストアファイルを削除してもう一度
152                 if failError?.domain == NSCocoaErrorDomain && (failError?.code == 134130 || failError?.code == 134110) && info.deleteAndRetry {
153                     self.removeDatabaseFile(info)
154                     do {
155                         try coordinator!.addPersistentStore(ofType: info.storeType, configurationName: nil, at: url, options: info.storeOptions)
156                         failError = nil
157                     }
158                     catch {
159                         failError = error as NSError
160                     }
161                 }
162             }
163         }
164         
165         if shouldFail || (failError != nil) {
166             if let error = failError {
167                 NSApplication.shared().presentError(error)
168             }
169             throw CoreDataError.couldNotCreateCoordinator(failureReason)
170         }
171         return coordinator!
172     }
173     private class func createManagedObjectContext(_ coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
174         let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
175         moc.persistentStoreCoordinator = coordinator
176         moc.undoManager = nil
177         return moc
178     }
179     private class func removeDatabaseFile(_ info: CoreDataIntormation) {
180         CoreDataRemover.remove(name: info.storeFileName)
181     }
182 }
183
184 extension CoreDataManager where Self: CoreDataProvider {
185     func removeDatabaseFile() {
186         CoreDataRemover.remove(name: self.core.info.storeFileName)
187     }
188 }
189
190 extension CoreDataProvider {
191     func saveActionCore() {
192         if !managedObjectContext.commitEditing() {
193             NSLog("\(String(describing: type(of: self))) unable to commit editing before saveing")
194             return
195         }
196         do { try managedObjectContext.save() }
197         catch { presentOnMainThread(error) }
198         if let p = managedObjectContext.parent {
199             p.performAndWait {
200                 do { try p.save() }
201                 catch { self.presentOnMainThread(error) }
202             }
203         }
204     }
205     private func presentOnMainThread(_ error: Error) {
206         if Thread.isMainThread {
207             NSApp.presentError(error)
208         } else {
209             DispatchQueue.main.sync {
210                 let _ = NSApp.presentError(error)
211             }
212         }
213     }
214 }
215
216 extension CoreDataAccessor {
217     func insertNewObject(for entity: Entity) -> NSManagedObject {
218         return NSEntityDescription.insertNewObject(forEntityName: entity.name, into: managedObjectContext)
219     }
220     func delete(_ object: NSManagedObject) {
221         managedObjectContext.delete(object)
222     }
223     func object(with objectId: NSManagedObjectID) -> NSManagedObject {
224         return managedObjectContext.object(with: objectId)
225     }
226     func objects(with entity: Entity, sortDescriptors: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) throws -> [NSManagedObject] {
227         let req = NSFetchRequest<NSManagedObject>(entityName: entity.name)
228         req.sortDescriptors = sortDescriptors
229         req.predicate = predicate
230         return try managedObjectContext.fetch(req)
231     }
232 }