OSDN Git Service

UIを調整
[kcd/KCD.git] / KCD / HMCoreDataManager.m
1 //
2 //  HMCoreDataManager.m
3 //  KCD
4 //
5 //  Created by Hori,Masaki on 2014/02/20.
6 //  Copyright (c) 2014年 Hori,Masaki. All rights reserved.
7 //
8
9 #import "HMCoreDataManager.h"
10
11 #import <objc/runtime.h>
12
13 typedef NS_ENUM(NSUInteger, HMCoreDataManagerType) {
14     readerType,
15     editorType,
16 };
17
18 @interface HMCoreDataManager ()
19 @property HMCoreDataManagerType type;
20 @end
21
22 @implementation HMCoreDataManager
23
24 @synthesize managedObjectContext = _managedObjectContext;
25
26 + (instancetype)defaultManager
27 {
28         HMCoreDataManager *defaultManager = objc_getAssociatedObject(self, "defaultManager");
29         
30         if(defaultManager) return defaultManager;
31         
32         defaultManager = [self new];
33         defaultManager.type = readerType;
34         
35         [[defaultManager managedObjectContext] setStalenessInterval:0.0];
36         
37         NSNotificationCenter *nc = [NSNotificationCenter defaultCenter];
38         [nc addObserver:defaultManager
39                    selector:@selector(applicationWillTerminate:)
40                            name:NSApplicationWillTerminateNotification
41                          object:NSApp];
42         
43         objc_setAssociatedObject(self, "defaultManager", defaultManager, OBJC_ASSOCIATION_RETAIN);
44         return defaultManager;
45 }
46
47 + (instancetype)oneTimeEditor
48 {
49         HMCoreDataManager *result = [self new];
50         result.type = editorType;
51         
52         return result;
53 }
54
55 - (void)dealloc
56 {
57         [self saveAction:nil];
58 }
59
60 - (NSArray *)objectsWithEntityName:(NSString *)entityName sortDescriptors:(NSArray *)sortDescriptors predicate:(NSPredicate *)predicate error:(NSError **)error
61 {
62         NSFetchRequest *req = [NSFetchRequest fetchRequestWithEntityName:entityName];
63         [req setPredicate:predicate];
64         [req setSortDescriptors:sortDescriptors];
65         
66         NSArray *array = [self.managedObjectContext executeFetchRequest:req error:error];
67         return array;
68 }
69 - (NSArray *)objectsWithEntityName:(NSString *)entityName sortDescriptors:(NSArray *)sortDescriptors error:(NSError **)error predicateFormat:(NSString *)format, ...
70 {
71         va_list ap;
72         va_start(ap, format);
73         NSPredicate *predicate = [NSPredicate predicateWithFormat:format arguments:ap];
74         va_end(ap);
75         return [self objectsWithEntityName:entityName sortDescriptors:sortDescriptors predicate:predicate error:error];
76 }
77
78 - (NSArray *)objectsWithEntityName:(NSString *)entityName predicate:(NSPredicate *)predicate error:(NSError **)error
79 {
80         return [self objectsWithEntityName:entityName sortDescriptors:nil predicate:predicate error:error];
81 }
82 - (NSArray *)objectsWithEntityName:(NSString *)entityName error:(NSError **)error predicateFormat:(NSString *)format, ...
83 {
84         va_list ap;
85         va_start(ap, format);
86         NSPredicate *predicate = [NSPredicate predicateWithFormat:format arguments:ap];
87         va_end(ap);
88         return [self objectsWithEntityName:entityName sortDescriptors:nil predicate:predicate error:error];
89 }
90
91 // Returns the directory the application uses to store the Core Data store file. This code uses a directory named "com.masakih.KanColleLevelManager" in the user's Application Support directory.
92 - (NSURL *)applicationFilesDirectory
93 {
94     NSFileManager *fileManager = [NSFileManager defaultManager];
95     NSURL *appSupportURL = [[fileManager URLsForDirectory:NSApplicationSupportDirectory inDomains:NSUserDomainMask] lastObject];
96     return [appSupportURL URLByAppendingPathComponent:@"com.masakih.KCD"];
97 }
98
99 // Creates if necessary and returns the managed object model for the application.
100 - (NSManagedObjectModel *)managedObjectModel
101 {
102         id managedObjectModel = objc_getAssociatedObject([self class], "managedObjectModel");
103     if (managedObjectModel) {
104         return managedObjectModel;
105     }
106         
107     NSURL *modelURL = [[NSBundle mainBundle] URLForResource:self.modelName withExtension:@"momd"];
108     managedObjectModel = [[NSManagedObjectModel alloc] initWithContentsOfURL:modelURL];
109         objc_setAssociatedObject([self class], "managedObjectModel", managedObjectModel, OBJC_ASSOCIATION_RETAIN);
110     return managedObjectModel;
111 }
112
113 // Returns the persistent store coordinator for the application. This implementation creates and return a coordinator, having added the store for the application to it. (The directory for the store is created, if necessary.)
114 - (NSPersistentStoreCoordinator *)persistentStoreCoordinator
115 {
116         id persistentStoreCoordinator = objc_getAssociatedObject([self class], "persistentStoreCoordinator");
117     if (persistentStoreCoordinator) {
118         return persistentStoreCoordinator;
119     }
120     
121     NSManagedObjectModel *mom = [self managedObjectModel];
122     if (!mom) {
123         NSLog(@"%@:%@ No model to generate a store from", [self class], NSStringFromSelector(_cmd));
124         return nil;
125     }
126     
127     NSFileManager *fileManager = [NSFileManager defaultManager];
128     NSURL *applicationFilesDirectory = [self applicationFilesDirectory];
129     NSError *error = nil;
130     
131     NSDictionary *properties = [applicationFilesDirectory resourceValuesForKeys:@[NSURLIsDirectoryKey] error:&error];
132     
133     if (!properties) {
134         BOOL ok = NO;
135         if ([error code] == NSFileReadNoSuchFileError) {
136             ok = [fileManager createDirectoryAtPath:[applicationFilesDirectory path] withIntermediateDirectories:YES attributes:nil error:&error];
137         }
138         if (!ok) {
139             [[NSApplication sharedApplication] presentError:error];
140             return nil;
141         }
142     } else {
143         if (![properties[NSURLIsDirectoryKey] boolValue]) {
144             // Customize and localize this error.
145             NSString *failureDescription = [NSString stringWithFormat:@"Expected a folder to store application data, found a file (%@).", [applicationFilesDirectory path]];
146             
147             NSMutableDictionary *dict = [NSMutableDictionary dictionary];
148             [dict setValue:failureDescription forKey:NSLocalizedDescriptionKey];
149             error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:101 userInfo:dict];
150             
151             [[NSApplication sharedApplication] presentError:error];
152             return nil;
153         }
154     }
155         NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:self.storeFileName];
156     NSPersistentStoreCoordinator *coordinator = [[NSPersistentStoreCoordinator alloc] initWithManagedObjectModel:mom];
157         NSPersistentStore *store = [coordinator addPersistentStoreWithType:self.storeType
158                                                                                                                  configuration:nil
159                                                                                                                                    URL:url
160                                                                                                                            options:self.storeOptions
161                                                                                                                                  error:&error];
162     if (!store) {
163                 // Data Modelが更新されていたらストアファイルを削除してもう一度
164                 if([[error domain] isEqualToString:NSCocoaErrorDomain] && [error code] == 134130 && self.deleteAndRetry) {
165                         [self removeDatabaseFile];
166                         store = [coordinator addPersistentStoreWithType:self.storeType
167                                                                                           configuration:nil
168                                                                                                                 URL:url
169                                                                                                         options:self.storeOptions
170                                                                                                           error:&error];
171                         if (!store) {
172                                 [[NSApplication sharedApplication] presentError:error];
173                                 return nil;
174                         }
175                 } else {
176                         [[NSApplication sharedApplication] presentError:error];
177                         return nil;
178                 }
179     }
180         
181         objc_setAssociatedObject([self class], "persistentStoreCoordinator", coordinator, OBJC_ASSOCIATION_RETAIN);
182     
183     return coordinator;
184 }
185
186 // Returns the managed object context for the application (which is already bound to the persistent store coordinator for the application.)
187 - (NSManagedObjectContext *)managedObjectContext
188 {
189     if (_managedObjectContext) {
190         return _managedObjectContext;
191     }
192     
193         if(self.type == readerType) {
194                 NSPersistentStoreCoordinator *coordinator = [self persistentStoreCoordinator];
195                 if (!coordinator) {
196                         NSMutableDictionary *dict = [NSMutableDictionary dictionary];
197                         [dict setValue:@"Failed to initialize the store" forKey:NSLocalizedDescriptionKey];
198                         [dict setValue:@"There was an error building up the data file." forKey:NSLocalizedFailureReasonErrorKey];
199                         NSError *error = [NSError errorWithDomain:@"YOUR_ERROR_DOMAIN" code:9999 userInfo:dict];
200                         [[NSApplication sharedApplication] presentError:error];
201                         return nil;
202                 }
203                 _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSMainQueueConcurrencyType];
204                 [_managedObjectContext setPersistentStoreCoordinator:coordinator];
205         } else {
206                 _managedObjectContext = [[NSManagedObjectContext alloc] initWithConcurrencyType:NSPrivateQueueConcurrencyType];
207                 _managedObjectContext.parentContext = [[[self class] defaultManager] managedObjectContext];
208         }
209         
210         _managedObjectContext.undoManager = nil;
211         
212     return _managedObjectContext;
213 }
214
215 // Performs the save action for the application, which is to send the save: message to the application's managed object context. Any encountered errors are presented to the user.
216 - (IBAction)saveAction:(id)sender
217 {
218     NSError *error = nil;
219     
220     if (![[self managedObjectContext] commitEditing]) {
221         NSLog(@"%@:%@ unable to commit editing before saving", [self class], NSStringFromSelector(_cmd));
222     }
223     
224     if (![[self managedObjectContext] save:&error]) {
225                 if([NSThread isMainThread]) {
226                         [[NSApplication sharedApplication] presentError:error];
227                 } else {
228                         dispatch_sync(dispatch_get_main_queue(), ^{
229                                 [[NSApplication sharedApplication] presentError:error];
230                         });
231                 }
232     }
233         
234         NSManagedObjectContext *mainContext = self.managedObjectContext.parentContext;
235         [mainContext performBlock:^{  // do nothing if mainContext is nil.
236                 NSError *error = nil;
237                 if(![mainContext save:&error]) {
238                         if([NSThread isMainThread]) {
239                                 [[NSApplication sharedApplication] presentError:error];
240                         } else {
241                                 dispatch_sync(dispatch_get_main_queue(), ^{
242                                         [[NSApplication sharedApplication] presentError:error];
243                                 });
244                         }
245                 }
246         }];
247 }
248
249 - (void)applicationWillTerminate:(NSNotification *)notification
250 {
251         [self saveAction:nil];
252 }
253
254
255 - (void)removeDatabaseFileAtURL:(NSURL *)url
256 {
257         NSFileManager *fileManager = [NSFileManager defaultManager];
258         
259         const char *filesystemRep = url.fileSystemRepresentation;
260         NSString *path = [NSString stringWithUTF8String:filesystemRep];
261         if(![fileManager fileExistsAtPath:path]) {
262                 NSLog(@"Could not find file for url (%@)", url);
263                 return;
264         }
265         NSError *error = nil;
266         [fileManager removeItemAtURL:url error:&error];
267         if(error) {
268                 NSLog(@"Could not remove file for URL (%@)", url);
269         }
270 }
271 - (void)removeDatabaseFile
272 {
273         NSURL *applicationFilesDirectory = [self applicationFilesDirectory];
274         NSString *baseName = self.storeFileName;
275         
276         for(NSString *suffix in @[@"", @"-wal", @"-shm"]) {
277                 NSString *fileName = [baseName stringByAppendingString:suffix];
278                 NSURL *url = [applicationFilesDirectory URLByAppendingPathComponent:fileName];
279                 [self removeDatabaseFileAtURL:url];
280         }
281 }
282
283 #pragma mark - abstruct
284 - (NSString *)modelName
285 {
286         [NSException raise:@"Abstract method" format:@"%s is abstract.", __PRETTY_FUNCTION__];
287         return nil;
288 }
289 - (NSString *)storeFileName
290 {
291         [NSException raise:@"Abstract method" format:@"%s is abstract.", __PRETTY_FUNCTION__];
292         return nil;
293 }
294 - (NSString *)storeType
295 {
296         [NSException raise:@"Abstract method" format:@"%s is abstract.", __PRETTY_FUNCTION__];
297         return nil;
298 }
299 - (NSDictionary *)storeOptions
300 {
301         [NSException raise:@"Abstract method" format:@"%s is abstract.", __PRETTY_FUNCTION__];
302         return nil;
303 }
304 - (BOOL)deleteAndRetry
305 {
306         [NSException raise:@"Abstract method" format:@"%s is abstract.", __PRETTY_FUNCTION__];
307         return NO;
308 }
309
310 @end