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 fileName: String
25     let options: [AnyHashable: Any]
26     let type: String
27     let tryRemake: Bool
28     
29     private static let defaultOptions: [AnyHashable: Any] = [
30         NSMigratePersistentStoresAutomaticallyOption: true,
31         NSInferMappingModelAutomaticallyOption: true
32     ]
33     
34     init(_ modelName: String,
35          fileName: String? = nil,
36          options: [AnyHashable: Any] = defaultOptions,
37          type: String = NSSQLiteStoreType,
38          tryRemake: Bool = false) {
39         self.modelName = modelName
40         self.fileName = fileName ?? "\(modelName).storedata"
41         self.options = options
42         self.type = type
43         self.tryRemake = tryRemake
44     }
45 }
46
47 struct CoreDataCore {
48     let info: CoreDataIntormation
49     let model: NSManagedObjectModel
50     let coordinator: NSPersistentStoreCoordinator
51     let parentContext: NSManagedObjectContext
52     
53     init(_ info: CoreDataIntormation) {
54         self.info = info
55         do {
56             let genaratee = try MocGenerater.genarate(info)
57             (self.model, self.coordinator, self.parentContext) = genaratee
58         } catch {
59             NSApplication.shared().presentError(error)
60             fatalError("CoreDataCore: can not initialize. \(error)")
61         }
62     }
63     
64     func editorContext() -> NSManagedObjectContext {
65         let moc = NSManagedObjectContext(concurrencyType: .privateQueueConcurrencyType)
66         moc.parent = parentContext
67         moc.undoManager = nil
68         return moc
69     }
70 }
71
72
73 protocol CoreDataProvider {
74     init(type: CoreDataManagerType)
75     var core: CoreDataCore { get }
76     var context: NSManagedObjectContext { get }
77     func save()
78 }
79
80 protocol CoreDataManager {
81     associatedtype InstanceType = Self
82     
83     static var `default`: InstanceType { get }
84     static func oneTimeEditor() -> InstanceType
85     
86     func removeDataFile()
87 }
88
89 protocol CoreDataAccessor: CoreDataProvider {
90     func insertNewObject<T>(for entity: Entity<T>) -> T?
91     func delete(_ object: NSManagedObject)
92     func object(with objectId: NSManagedObjectID) -> NSManagedObject
93     func objects<T>(with entity: Entity<T>, sortDescriptors: [NSSortDescriptor]?, predicate: NSPredicate?) throws -> [T]
94 }
95
96 private class CoreDataRemover {
97     class func remove(name: String) {
98         ["", "-wal", "-shm"]
99             .map { name + $0 }
100             .map { ApplicationDirecrories.support.appendingPathComponent($0) }
101             .forEach { removeDataFile(at: $0) }
102     }
103     private class func removeDataFile(at url: URL) {
104         do {
105             try FileManager.default.removeItem(at: url)
106         } catch {
107             print("Could not remove file for URL (\(url))")
108         }
109     }
110 }
111
112 private class MocGenerater {
113     class func genarate(_ info: CoreDataIntormation) throws ->
114         (model: NSManagedObjectModel, coordinator: NSPersistentStoreCoordinator, moc: NSManagedObjectContext) {
115             do {
116                 let model = try createModel(info)
117                 let coordinator = try getCoordinator(info, model)
118                 let moc = createContext(coordinator)
119                 return (model: model, coordinator: coordinator, moc: moc)
120             } catch {
121                 throw error
122             }
123     }
124     
125     private class func createModel(_ info: CoreDataIntormation) throws -> NSManagedObjectModel {
126         let modelURL = Bundle.main.url(forResource: info.modelName, withExtension: "momd")!
127         guard let model = NSManagedObjectModel(contentsOf: modelURL) else {
128             throw CoreDataError.couldNotCreateModel
129         }
130         return model
131     }
132     private class func getCoordinator(_ info: CoreDataIntormation,
133                                       _ model: NSManagedObjectModel) throws -> NSPersistentStoreCoordinator {
134         do {
135             return try createCoordinator(info, model)
136         } catch {
137             let nserror = error as NSError
138             // Data Modelが更新されていたらストアファイルを削除してもう一度
139             if nserror.domain == NSCocoaErrorDomain,
140                 (nserror.code == 134130 || nserror.code == 134110),
141                 info.tryRemake {
142                 self.removeDataFile(info)
143                 do {
144                     return try createCoordinator(info, model)
145                 } catch {
146                     print("Fail crrate NSPersistentStoreCoordinator twice.")
147                 }
148             }
149             throw error
150         }
151     }
152     private class func createCoordinator(_ info: CoreDataIntormation,
153                                          _ model: NSManagedObjectModel) throws -> NSPersistentStoreCoordinator {
154         if !checkDirectory(ApplicationDirecrories.support) {
155             let failureReason = "Can not use directory \(ApplicationDirecrories.support.path)"
156             throw CoreDataError.couldNotCreateCoordinator(failureReason)
157         }
158         
159         let coordinator: NSPersistentStoreCoordinator = NSPersistentStoreCoordinator(managedObjectModel: model)
160         let url = ApplicationDirecrories.support.appendingPathComponent(info.fileName)
161         do {
162             try coordinator.addPersistentStore(ofType: info.type,
163                                                configurationName: nil,
164                                                at: url,
165                                                options: info.options)
166         } catch {
167             throw error
168         }
169         return coordinator
170     }
171     private class func createContext(_ coordinator: NSPersistentStoreCoordinator) -> NSManagedObjectContext {
172         let moc = NSManagedObjectContext(concurrencyType: .mainQueueConcurrencyType)
173         moc.persistentStoreCoordinator = coordinator
174         moc.undoManager = nil
175         return moc
176     }
177     private class func removeDataFile(_ info: CoreDataIntormation) {
178         CoreDataRemover.remove(name: info.fileName)
179     }
180 }
181
182 extension CoreDataManager where Self: CoreDataProvider {
183     func removeDataFile() {
184         CoreDataRemover.remove(name: self.core.info.fileName)
185     }
186 }
187
188 extension CoreDataProvider {
189     func save() {
190         if !context.commitEditing() {
191             print("\(String(describing: type(of: self))) unable to commit editing before saveing")
192             return
193         }
194         do {
195             try context.save()
196         } catch { presentOnMainThread(error) }
197         if let p = context.parent {
198             p.performAndWait {
199                 do {
200                     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<T>(for entity: Entity<T>) -> T? {
218         return NSEntityDescription
219             .insertNewObject(forEntityName: entity.name,
220                              into: context) as? T
221     }
222     func delete(_ object: NSManagedObject) {
223         context.delete(object)
224     }
225     func object(with objectId: NSManagedObjectID) -> NSManagedObject {
226         return context.object(with: objectId)
227     }
228     func objects<T>(with entity: Entity<T>,
229                     sortDescriptors: [NSSortDescriptor]? = nil,
230                     predicate: NSPredicate? = nil) throws -> [T] {
231         let req = NSFetchRequest<T>(entityName: entity.name)
232         req.sortDescriptors = sortDescriptors
233         req.predicate = predicate
234         return try context.fetch(req)
235     }
236 }