OSDN Git Service

エンティティ名とクラス名を同じにしたことで生じた無駄を削除
[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
53 protocol CoreDataProvider {
54     init(type: CoreDataManagerType)
55     var core: CoreDataCore { get }
56     var managedObjectContext: NSManagedObjectContext { get }
57     func saveActionCore()
58 }
59
60 protocol CoreDataManager {
61     associatedtype InstanceType = Self
62     
63     static var `default`: InstanceType { get }
64     static func oneTimeEditor() -> InstanceType
65     
66     func removeDatabaseFile()
67 }
68
69 protocol CoreDataAccessor: CoreDataProvider {
70     func insertNewObject<T>(for entity: Entity<T>) -> T?
71     func delete(_ object: NSManagedObject)
72     func object(with objectId: NSManagedObjectID) -> NSManagedObject
73     func objects<T>(with entity: Entity<T>, sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) throws -> [T]
74 }
75
76 private class CoreDataRemover {
77     class func remove(name: String) {
78         ["", "-wal", "-shm"]
79             .map { name + $0 }
80             .map { ApplicationDirecrories.support.appendingPathComponent($0) }
81             .forEach { removeDatabaseFileAtURL(url: $0) }
82     }
83     private class func removeDatabaseFileAtURL(url: URL) {
84         do {
85             try FileManager.default.removeItem(at: url)
86         }
87         catch {
88             print("Could not remove file for URL (\(url))");
89         }
90     }
91 }
92
93 private class MocGenerater {
94     class func genarate(_ info: CoreDataIntormation) throws -> (model: NSManagedObjectModel, coordinator: NSPersistentStoreCoordinator, moc: NSManagedObjectContext) {
95         do {
96             let model = try createManagedObjectModel(info)
97             let coordinator = try createPersistentStoreCoordinator(info, model)
98             let moc = createManagedObjectContext(coordinator)
99             return (model: model, coordinator: coordinator, moc: moc)
100         }
101         catch {
102             throw error
103         }
104     }
105     
106     private class func createManagedObjectModel(_ info: CoreDataIntormation) throws -> NSManagedObjectModel {
107         let modelURL = Bundle.main.url(forResource: info.modelName, withExtension: "momd")!
108         guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
109             throw CoreDataError.couldNotCreateModel
110         }
111         return model
112     }
113     
114     private class func createPersistentStoreCoordinator(_ info: CoreDataIntormation, _ model: NSManagedObjectModel) throws -> NSPersistentStoreCoordinator {
115         var failError: NSError? = nil
116         var shouldFail = false
117         var failureReason = "There was an error creating or loading the application's saved data."
118         
119         do {
120             let p = try ApplicationDirecrories.support.resourceValues(forKeys: [.isDirectoryKey])
121             if !p.isDirectory! {
122                 failureReason = "Expected a folder to store application data, found a file \(ApplicationDirecrories.support.path)."
123                 shouldFail = true
124             }
125         }
126         catch {
127             let nserror = error as NSError
128             if nserror.code == NSFileReadNoSuchFileError {
129                 do {
130                     try FileManager.default.createDirectory(at: ApplicationDirecrories.support, withIntermediateDirectories: false, attributes: nil)
131                 } catch {
132                     failError = nserror
133                 }
134             } else {
135                 failError = nserror
136             }
137         }
138         
139         var coordinator: NSPersistentStoreCoordinator? = nil
140         if failError == nil {
141             coordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
142             let url = ApplicationDirecrories.support.appendingPathComponent(info.storeFileName)
143             do {
144                 try coordinator!.addPersistentStore(ofType: info.storeType, configurationName: nil, at: url, options: info.storeOptions)
145             } catch {
146                 failError = error as NSError
147                 
148                 // Data Modelが更新されていたらストアファイルを削除してもう一度
149                 if failError?.domain == NSCocoaErrorDomain && (failError?.code == 134130 || failError?.code == 134110) && info.deleteAndRetry {
150                     self.removeDatabaseFile(info)
151                     do {
152                         try coordinator!.addPersistentStore(ofType: info.storeType, configurationName: nil, at: url, options: info.storeOptions)
153                         failError = nil
154                     }
155                     catch {
156                         failError = error as NSError
157                     }
158                 }
159             }
160         }
161         
162         if shouldFail || (failError != nil) {
163             if let error = failError {
164                 NSApplication.shared().presentError(error)
165             }
166             throw CoreDataError.couldNotCreateCoordinator(failureReason)
167         }
168         return coordinator!
169     }
170     private class func createManagedObjectContext(_ coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
171         let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
172         moc.persistentStoreCoordinator = coordinator
173         moc.undoManager = nil
174         return moc
175     }
176     private class func removeDatabaseFile(_ info: CoreDataIntormation) {
177         CoreDataRemover.remove(name: info.storeFileName)
178     }
179 }
180
181 extension CoreDataManager where Self: CoreDataProvider {
182     func removeDatabaseFile() {
183         CoreDataRemover.remove(name: self.core.info.storeFileName)
184     }
185 }
186
187 extension CoreDataProvider {
188     func saveActionCore() {
189         if !managedObjectContext.commitEditing() {
190             NSLog("\(String(describing: type(of: self))) unable to commit editing before saveing")
191             return
192         }
193         do { try managedObjectContext.save() }
194         catch { presentOnMainThread(error) }
195         if let p = managedObjectContext.parent {
196             p.performAndWait {
197                 do { try p.save() }
198                 catch { self.presentOnMainThread(error) }
199             }
200         }
201     }
202     private func presentOnMainThread(_ error: Error) {
203         if Thread.isMainThread {
204             NSApp.presentError(error)
205         } else {
206             DispatchQueue.main.sync {
207                 let _ = NSApp.presentError(error)
208             }
209         }
210     }
211 }
212
213 extension CoreDataAccessor {
214     func insertNewObject<T>(for entity: Entity<T>) -> T? {
215         return NSEntityDescription
216             .insertNewObject(forEntityName: entity.name
217                 , into: managedObjectContext) as? T
218     }
219     func delete(_ object: NSManagedObject) {
220         managedObjectContext.delete(object)
221     }
222     func object(with objectId: NSManagedObjectID) -> NSManagedObject {
223         return managedObjectContext.object(with: objectId)
224     }
225     func objects<T>(with entity: Entity<T>, sortDescriptors: [NSSortDescriptor]? = nil, predicate: NSPredicate? = nil) throws -> [T] {
226         let req = NSFetchRequest<T>(entityName: entity.name)
227         req.sortDescriptors = sortDescriptors
228         req.predicate = predicate
229         return try managedObjectContext.fetch(req)
230     }
231 }