root/branches/CoreData/SapphireFrappliance/MetaData/Support/SapphireMetaDataSupport.m @ 840

Revision 840, 26.1 KB (checked in by gbooker, 16 months ago)

Delete unused objects as they become unused so as to not need the prune call later. This will help accelerate things later. Also, handled XML a bit better.

Line 
1/*
2 * SapphireMetaDataSupport.m
3 * Sapphire
4 *
5 * Created by Graham Booker on Apr. 16, 2008.
6 * Copyright 2008 Sapphire Development Team and/or www.nanopi.net
7 * All rights reserved.
8 *
9 * This program is free software; you can redistribute it and/or modify it under the terms of the GNU
10 * General Public License as published by the Free Software Foundation; either version 3 of the License,
11 * or (at your option) any later version.
12 *
13 * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even
14 * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General
15 * Public License for more details.
16 *
17 * You should have received a copy of the GNU General Public License along with this program; if not,
18 * write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
19 */
20
21#import "SapphireMetaDataSupport.h"
22#import "SapphireDirectoryMetaData.h"
23#import "SapphireFileMetaData.h"
24#import "SapphireJoinedFile.h"
25#import "SapphireCollectionDirectory.h"
26#import "SapphireMovie.h"
27#import "SapphireCast.h"
28#import "SapphireMovieTranslation.h"
29#import "SapphireMoviePoster.h"
30#import "SapphireTVTranslation.h"
31#import "SapphireTVShow.h"
32#import "SapphireMetaDataUpgrading.h"
33#import "SapphireDirector.h"
34#import "SapphireGenre.h"
35#import "SapphireDirectorySymLink.h"
36#import "SapphireFileSymLink.h"
37#import "SapphireEpisode.h"
38#import "SapphireXMLData.h"
39#import "SapphireApplianceController.h"
40#import "SapphireSeason.h"
41#import "CoreDataSupportFunctions.h"
42
43@interface NSManagedObject (deleteDelegate)
44- (BOOL)shouldDelete;
45@end
46
47
48@interface NSManagedObject (ChangePersistence)
49- (NSDictionary *)changedValuesWithObjectIDs;
50- (void)updateChanges:(NSDictionary *)changes withTrans:(NSDictionary *)translation;
51@end
52
53@implementation NSManagedObject (ChangePersistence)
54- (NSDictionary *)changedValuesWithObjectIDs
55{
56        NSDictionary *changedValues = [self changedValues];
57        NSMutableDictionary *ret = [[NSMutableDictionary alloc] initWithDictionary:changedValues];
58        NSString *key;
59        NSEnumerator *keyEnum = [changedValues keyEnumerator];
60        while((key = [keyEnum nextObject]) != nil)
61        {
62                id value = [ret objectForKey:key];
63                if([value isKindOfClass:[NSManagedObject class]])
64                        [ret setObject:[[(NSManagedObject *)value objectID] URIRepresentation] forKey:key];
65                else if([value isKindOfClass:[NSSet class]] && [[(NSSet *)value anyObject] isKindOfClass:[NSManagedObject class]])
66                        [ret setObject:[value valueForKeyPath:@"objectID.URIRepresentation"] forKey:key];
67        }
68        return [ret autorelease];
69}
70
71- (void)updateChanges:(NSDictionary *)changes withTrans:(NSDictionary *)translation
72{
73        NSEnumerator *keyEnum = [changes keyEnumerator];
74        NSString *key;
75        while((key = [keyEnum nextObject]) != nil)
76        {
77                id newValue = [changes objectForKey:key];
78                id testValue = newValue;
79                BOOL isSet = NO;
80                if([newValue isKindOfClass:[NSSet class]])
81                {
82                        testValue = [newValue anyObject];
83                        isSet = YES;
84                }
85                NSManagedObjectContext *moc = [self managedObjectContext];
86                if([testValue isKindOfClass:[NSURL class]] && [[(NSURL *)testValue scheme] hasPrefix:@"x-core"])
87                {
88                        NSPersistentStoreCoordinator *coord = [moc persistentStoreCoordinator];
89                        NSManagedObject *newObj;
90                        if(isSet)
91                        {
92                                NSSet *objSet = (NSSet *)newValue;
93                                NSEnumerator *objEnum = [objSet objectEnumerator];
94                                NSMutableSet *newSet = [[NSMutableSet alloc] init];
95                                NSURL *url;
96                                while((url = [objEnum nextObject]) != nil)
97                                {
98                                        newObj = [translation objectForKey:url];
99                                        if(newObj == nil)
100                                        {
101                                                NSManagedObjectID *upObjId = [coord managedObjectIDForURIRepresentation:url];
102                                                newObj = [moc objectWithID:upObjId];
103                                        }
104                                        [newSet addObject:newObj];
105                                }
106                                [self setValue:newSet forKey:key];
107                                [newSet release];
108                        }
109                        else
110                        {
111                                newObj = [translation objectForKey:newValue];
112                                if(newObj == nil)
113                                {
114                                        NSManagedObjectID *upObjId = [coord managedObjectIDForURIRepresentation:newValue];
115                                        newObj = [moc objectWithID:upObjId];
116                                }
117                                [self setValue:newObj forKey:key];
118                        }
119                }
120                else if(![newValue isKindOfClass:[NSNull class]])
121                        [self setValue:newValue forKey:key];
122                else
123                        [self setValue:nil forKey:key];
124        }
125}
126
127@end
128
129#define META_VERSION_KEY                        @"Version"
130
131/* Movie Translations */
132#define MOVIE_TRAN_VERSION_KEY                                  @"Version"
133#define MOVIE_TRAN_CURRENT_VERSION                              2
134/* Translation Keys */
135#define MOVIE_TRAN_TRANSLATIONS_KEY                             @"Translations"
136#define MOVIE_TRAN_IMDB_LINK_KEY                                @"IMDB Link"
137#define MOVIE_TRAN_IMP_LINK_KEY                                 @"IMP Link"
138#define MOVIE_TRAN_IMP_POSTERS_KEY                              @"IMP Posters"
139#define MOVIE_TRAN_SELECTED_POSTER_KEY                  @"Selected Poster"
140#define MOVIE_TRAN_AUTO_SELECT_POSTER_KEY               @"Default Poster"
141
142#define CHANGES_INSERTED_KEY                                    @"Inserted"
143#define CHANGES_UPDATED_KEY                                             @"Updated"
144#define CHANGES_DELETED_KEY                                             @"Deleted"
145
146static NSSet *coverArtExtentions = nil;
147static NSMutableSet *pendingDeleteObjects = nil;
148
149NSString *searchCoverArtExtForPath(NSString *path)
150{
151        NSFileManager *fm = [NSFileManager defaultManager];
152        NSString *directory = [path stringByDeletingLastPathComponent];
153        NSArray *files = [fm directoryContentsAtPath:directory];
154        NSString *lastComp = [path lastPathComponent];
155        /*Search all files*/
156        NSEnumerator *fileEnum = [files objectEnumerator];
157        NSString *file = nil;
158        while((file = [fileEnum nextObject]) != nil)
159        {
160                NSString *ext = [file pathExtension];
161                if([ext length] &&
162                   [coverArtExtentions containsObject:ext] &&
163                   [lastComp caseInsensitiveCompare:[file stringByDeletingPathExtension]] == NSOrderedSame)
164                        return [directory stringByAppendingPathComponent:file];
165        }
166        /*Didn't find one*/
167        return nil;
168}
169
170@implementation SapphireMetaDataSupport
171
172+ (void)load
173{
174        coverArtExtentions = [[NSSet alloc] initWithObjects:
175                                                  @"jpg",
176                                                  @"jpeg",
177                                                  @"tif",
178                                                  @"tiff",
179                                                  @"png",
180                                                  @"gif",
181                                                  nil];
182        pendingDeleteObjects = [[NSMutableSet alloc] init];
183}
184
185+ (SapphireMetaDataSupport *)sharedInstance
186{
187        static SapphireMetaDataSupport *shared = nil;
188       
189        if(shared == nil)
190                shared = [[SapphireMetaDataSupport alloc] init];
191       
192        return shared;
193}
194
195- (void) dealloc
196{
197        [mainMoc release];
198        [writeTimer invalidate];
199        writeTimer = nil;
200        [super dealloc];
201}
202
203+ (void)pruneMetaData:(NSManagedObjectContext *)moc
204{
205        NSPredicate *movieFilePred = [NSPredicate predicateWithFormat:@"movie != nil"];
206        NSArray *movieFiles = doFetchRequest(SapphireFileMetaDataName, moc, movieFilePred);
207        NSSet *movieIds = [NSSet setWithArray:[movieFiles valueForKeyPath:@"movie.objectID"]];
208       
209        NSPredicate *movieNoFile = [NSPredicate predicateWithFormat:@"NOT SELF IN %@", movieIds];
210        NSArray *emptyMovies = doFetchRequest(SapphireMovieName, moc, movieNoFile);
211        SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning Movies %@", [emptyMovies valueForKeyPath:@"title"]);
212        NSEnumerator *objEnum = [emptyMovies objectEnumerator];
213        NSManagedObject *obj;
214        while((obj = [objEnum nextObject]) != nil)
215                [moc deleteObject:obj];
216       
217        NSArray *allMovies = doFetchRequest(SapphireMovieName, moc, nil);
218       
219        NSDictionary *pruneKeys = [NSDictionary dictionaryWithObjectsAndKeys:
220                                                           SapphireCastName, @"cast",
221                                                           SapphireGenreName, @"genres",
222                                                           SapphireDirectorName, @"directors",
223                                                           nil];
224        NSEnumerator *keyEnum = [pruneKeys keyEnumerator];
225        NSString *key;
226        while((key = [keyEnum nextObject]) != nil)
227        {
228                NSString *objName = [pruneKeys objectForKey:key];
229                NSArray *itemSet = [allMovies valueForKeyPath:[NSString stringWithFormat:@"@distinctUnionOfSets.%@.objectID", key]];
230                NSArray *emptyItems = doFetchRequest(objName, moc, [NSPredicate predicateWithFormat:@"NOT SELF IN %@", itemSet]);
231                SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning %@ %@", key, [emptyItems valueForKeyPath:@"name"]);
232                objEnum = [emptyItems objectEnumerator];
233                while((obj = [objEnum nextObject]) != nil)
234                        [moc deleteObject:obj];
235        }
236       
237        NSPredicate *epFilePred = [NSPredicate predicateWithFormat:@"tvEpisode != nil"];
238        NSArray *epFiles = doFetchRequest(SapphireFileMetaDataName, moc, epFilePred);
239        NSSet *epIds = [NSSet setWithArray:[epFiles valueForKeyPath:@"tvEpisode.objectID"]];
240       
241        NSPredicate *epNoFile = [NSPredicate predicateWithFormat:@"NOT SELF IN %@", epIds];
242        NSArray *emptyEpisodes = doFetchRequest(SapphireEpisodeName, moc, epNoFile);
243        SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning Episodes %@", [emptyEpisodes valueForKeyPath:@"episodeTitle"]);
244        objEnum = [emptyEpisodes objectEnumerator];
245        while((obj = [objEnum nextObject]) != nil)
246                [moc deleteObject:obj];
247       
248        NSArray *allEps = doFetchRequest(SapphireEpisodeName, moc, nil);
249       
250        NSSet *seasonIds = [allEps valueForKeyPath:@"@distinctUnionOfObjects.season.objectID"];
251        NSPredicate *noEpisodes = [NSPredicate predicateWithFormat:@"NOT SELF IN %@", seasonIds];
252        NSArray *emptySeasons = doFetchRequest(SapphireSeasonName, moc, noEpisodes);
253        SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning Seasons %@", [emptySeasons valueForKeyPath:@"path"]);
254        objEnum = [emptySeasons objectEnumerator];
255        while((obj = [objEnum nextObject]) != nil)
256                [moc deleteObject:obj];
257       
258        NSSet *showIds = [allEps valueForKeyPath:@"@distinctUnionOfObjects.tvShow.objectID"];
259        noEpisodes = [NSPredicate predicateWithFormat:@"NOT SELF IN %@", showIds];
260        NSArray *emptyShows = doFetchRequest(SapphireTVShowName, moc, noEpisodes);
261        SapphireLog(SAPPHIRE_LOG_IMPORT, SAPPHIRE_LOG_LEVEL_DETAIL, @"Pruning Shows %@", [emptyShows valueForKeyPath:@"name"]);
262        objEnum = [emptyShows objectEnumerator];
263        while((obj = [objEnum nextObject]) != nil)
264                [moc deleteObject:obj];
265}
266
267+ (void)setObjectForPendingDelete:(NSManagedObject *)objectToDelete
268{
269        [pendingDeleteObjects addObject:objectToDelete];
270}
271
272/*!
273 * @brief Deletes pending objects
274 *
275 * Deletes all objects in the pending queue, but calls shouldDelete first to make sure it should
276 * be deleted.  This is also used as a hack to work around CD apparent inability to make KVO
277 * notifications during an object delete when it is within an KVO notification.
278 */
279+ (void)deletePendingObjects
280{
281        NSManagedObjectContext *mainMoc = [SapphireMetaDataSupport sharedInstance]->mainMoc;
282        [mainMoc processPendingChanges];               
283        while([pendingDeleteObjects count])
284        {
285                NSManagedObject *obj;
286                NSSet *pendingObjects = [pendingDeleteObjects copy];
287                [pendingDeleteObjects removeAllObjects];
288                NSEnumerator *objEnum = [pendingObjects objectEnumerator];
289                while((obj = [objEnum nextObject]) != nil)
290                {
291                        if([obj isDeleted])
292                                continue;
293                        if([obj managedObjectContext] != mainMoc)
294                                continue;
295                        if(![obj respondsToSelector:@selector(shouldDelete)] || [obj shouldDelete])
296                                [mainMoc deleteObject:obj];
297                }
298                [pendingObjects release];
299                [mainMoc processPendingChanges];               
300        }
301}
302
303- (void)realWriteMetaData:(NSTimer *)timer
304{
305        NSManagedObjectContext *context = nil;
306        if([timer isKindOfClass:[NSManagedObjectContext class]])
307                context = (NSManagedObjectContext *)timer;
308        else
309                context = [timer userInfo];
310       
311        if(writeTimer != nil)
312                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Rescheduled write");
313        [writeTimer invalidate];
314        writeTimer = nil;
315        NSError *error = nil;
316        locked = NO;
317        BOOL success = NO;
318        @try {
319                [SapphireMetaDataSupport deletePendingObjects];
320                success = [context save:&error];
321        }
322        @catch (NSException * e) {
323                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Could not save due to exception \"%@\" with reason \"%@\"", [e name], [e reason]);
324        }
325        if(error != nil)
326        {
327                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Save error \"%@\"", error);
328                NSArray *details = [[error userInfo] objectForKey:@"NSDetailedErrors"];
329                if(details != nil)
330                {
331                        NSEnumerator *errorEnum = [details objectEnumerator];
332                        NSError *aError;
333                        while((aError = [errorEnum nextObject]) != nil)
334                                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"One error is %@: %@", aError, [aError userInfo]);
335                }
336                NSException *underlying = [[error userInfo] objectForKey:@"NSUnderlyingException"];
337                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Underlying is %@ %@ %@ %@", underlying, [underlying name], [underlying reason], [underlying userInfo]);
338                if([[underlying reason] isEqualToString:@"database is locked"])
339                {
340                        SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Detected locked");
341                        locked = YES;
342                }
343        }
344        if(success == NO)
345        {
346                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Inserted objects is %@", [context insertedObjects]);
347                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Updated objects is %@", [context updatedObjects]);
348                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DEBUG, @"Deleted objects is %@", [context deletedObjects]);
349                interval *= 2;
350                [writeTimer invalidate];
351                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Rescheduling write to occurr in %f seconds", interval);
352               
353                @try {
354                        NSSet *objSet = [context updatedObjects];
355                        NSEnumerator *objEnum = [objSet objectEnumerator];
356                        NSManagedObject *obj;
357                        while((obj = [objEnum nextObject]) != nil)
358                                [context refreshObject:obj mergeChanges:YES];
359                        objSet = [context deletedObjects];
360                        objEnum = [objSet objectEnumerator];
361                        while((obj = [objEnum nextObject]) != nil)
362                                [context refreshObject:obj mergeChanges:YES];                   
363                }
364                @catch (NSException * e) {
365                        SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Could not fix save due to exception \"%@\" with reason \"%@\"", [e name], [e reason]);
366                }
367               
368                writeTimer = [NSTimer scheduledTimerWithTimeInterval:interval target:self selector:@selector(realWriteMetaData:) userInfo:context repeats:NO];
369        }
370        else
371                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_DETAIL, @"Save successful");
372}
373
374- (BOOL)save:(NSManagedObjectContext *)context;
375{
376        if(context != mainMoc)
377        {
378                return YES;
379        }
380        if(writeTimer == nil)
381        {
382                interval = 1;
383                [self performSelectorOnMainThread:@selector(realWriteMetaData:) withObject:context waitUntilDone:YES];
384                return (writeTimer == nil);
385        }
386        else
387                return YES;
388}
389
390+ (BOOL)save:(NSManagedObjectContext *)context
391{
392        if(context == nil)
393                return NO;
394       
395        return [[SapphireMetaDataSupport sharedInstance] save:context];
396}
397
398- (void)applyChanges:(NSDictionary *)changes
399{
400        [SapphireMetaDataSupport applyChanges:changes toContext:mainMoc];
401}
402
403- (void)applyChangesFromContext:(NSManagedObjectContext *)context
404{
405        if(mainMoc != nil)
406        {
407                NSDictionary *changes = [SapphireMetaDataSupport changesDictionaryForContext:context];
408                [self performSelectorOnMainThread:@selector(applyChanges:) withObject:changes waitUntilDone:YES];
409        }
410}
411
412+ (void)applyChangesFromContext:(NSManagedObjectContext *)context
413{
414        [[SapphireMetaDataSupport sharedInstance] applyChangesFromContext:context];
415}
416
417- (void)setMainContext:(NSManagedObjectContext *)moc
418{
419        [mainMoc release];
420        mainMoc = [moc retain];
421}
422
423+ (void)setMainContext:(NSManagedObjectContext *)moc
424{
425        [[SapphireMetaDataSupport sharedInstance] setMainContext:moc];
426}
427
428- (BOOL)wasLocked
429{
430        return locked;
431}
432
433+ (BOOL)wasLocked
434{
435        return [[SapphireMetaDataSupport sharedInstance] wasLocked];
436}
437
438+ (void)importV1Store:(NSManagedObjectContext *)v1Context intoContext:(NSManagedObjectContext *)context withDisplay:(SapphireMetaDataUpgrading *)display
439{
440        [display setCurrentFile:@"Upgrading Cast"];
441        NSDictionary *castLookup = [SapphireCast upgradeV1CastFromContext:v1Context toContext:context];
442        [display setCurrentFile:@"Upgrading Directors"];
443        NSDictionary *directorLookup = [SapphireDirector upgradeV1DirectorsFromContext:v1Context toContext:context];
444        [display setCurrentFile:@"Upgrading Genres"];
445        NSDictionary *genreLookup = [SapphireGenre upgradeV1GenresFromContext:v1Context toContext:context];
446        [display setCurrentFile:@"Upgrading Movies"];
447        NSDictionary *movieLookup = [SapphireMovie upgradeV1MoviesFromContext:v1Context toContext:context withCast:castLookup directors:directorLookup genres:genreLookup];
448        [display setCurrentFile:@"Upgrading Shows"];
449        [SapphireTVShow upgradeV1ShowsFromContext:v1Context toContext:context];
450        [display setCurrentFile:@"Upgrading Directories"];
451        NSDictionary *dirLookup = [SapphireDirectoryMetaData upgradeV1DirectoriesFromContext:v1Context toContext:context];
452        [display setCurrentFile:@"Upgrading Files"];
453        NSDictionary *fileLookup = [SapphireFileMetaData upgradeV1FilesFromContext:v1Context toContext:context withMovies:movieLookup directories:dirLookup];
454        [display setCurrentFile:@"Upgrading SymLinks"];
455        [SapphireDirectorySymLink upgradeV1DirLinksFromContext:v1Context toContext:context directories:dirLookup];
456        [SapphireFileSymLink upgradeV1FileLinksFromContext:v1Context toContext:context directories:dirLookup file:fileLookup];
457        [display setCurrentFile:@"Upgrading Joined Files"];
458        [SapphireJoinedFile upgradeV1JoinedFileFromContext:v1Context toContext:context file:fileLookup];
459        [display setCurrentFile:@"Upgrading Episodes"];
460        [SapphireEpisode upgradeV1EpisodesFromContext:v1Context toContext:context file:fileLookup];
461        [display setCurrentFile:@"Upgrading XML"];
462        [SapphireXMLData upgradeV1XMLFromContext:v1Context toContext:context file:fileLookup];
463}
464
465+ (void)importPlist:(NSString *)configDir intoContext:(NSManagedObjectContext *)context withDisplay:(SapphireMetaDataUpgrading *)display
466{
467        NSDictionary *dict = [NSDictionary dictionaryWithContentsOfFile:[configDir stringByAppendingPathComponent:@"metaData.plist"]];
468        NSMutableDictionary *defer = [NSMutableDictionary dictionaryWithObjectsAndKeys:
469                                                                  [NSMutableDictionary dictionary], @"Join",
470                                                                  [NSMutableDictionary dictionary], @"Cast",
471                                                                  [NSMutableDictionary dictionary], @"Directors",
472                                                                  nil];
473        int version = [[dict objectForKey:META_VERSION_KEY] intValue];
474        SapphireDirectoryMetaData *newDir = nil;
475        if(version > 2)
476        {
477                NSDictionary *slash = [dict objectForKey:@"/"];
478                newDir = [SapphireDirectoryMetaData createDirectoryWithPath:@"/" parent:nil inContext:context];
479                [newDir insertDictionary:slash withDefer:defer andDisplay:display];
480        }
481        else
482        {
483                newDir = [SapphireDirectoryMetaData createDirectoryWithPath:[NSHomeDirectory() stringByAppendingPathComponent:@"Movies"] parent:nil inContext:context];
484                [newDir insertDictionary:dict withDefer:defer andDisplay:display];
485        }
486        [display setCurrentFile:BRLocalizedString(@"Upgrading Joined Files", @"")];
487        NSDictionary *joinDict = [defer objectForKey:@"Join"];
488        if(joinDict != nil)
489        {
490                NSEnumerator *joinEunm = [joinDict keyEnumerator];
491                NSString *joinedPath;
492                while((joinedPath = [joinEunm nextObject]) != nil)
493                {
494                        SapphireJoinedFile *joinedFile = [SapphireJoinedFile joinedFileForPath:joinedPath inContext:context];
495                        NSArray *joinArray = [joinDict objectForKey:joinedPath];
496                        NSEnumerator *joinedEnum = [joinArray objectEnumerator];
497                        SapphireFileMetaData *joinFile;
498                        while((joinFile = [joinedEnum nextObject]) != nil)
499                                joinFile.joinedToFile = joinedFile;
500                }                       
501        }
502       
503        [display setCurrentFile:BRLocalizedString(@"Upgrading Collection Prefs", @"")];
504        NSDictionary *options = [dict objectForKey:@"Options"];
505        NSMutableSet *collections = [NSMutableSet set];
506        NSArray *custom = [options objectForKey:@"Directories"];
507        if([custom count])
508                [collections unionSet:[NSSet setWithArray:custom]];
509        NSDictionary *hidden = [options objectForKey:@"Hide"];
510        NSArray *keyArray = [hidden allKeys];
511        if([keyArray count])
512                [collections unionSet:[NSSet setWithArray:keyArray]];
513        NSDictionary *skipped = [options objectForKey:@"Skip"];
514        keyArray = [skipped allKeys];
515        if([keyArray count])
516                [collections unionSet:[NSSet setWithArray:keyArray]];
517       
518        NSEnumerator *collectionEnum = [collections objectEnumerator];
519        NSString *collectionPath;
520        while((collectionPath = [collectionEnum nextObject]) != nil)
521        {
522                [SapphireCollectionDirectory collectionAtPath:collectionPath
523                                                                                                mount:NO
524                                                                                                 skip:[[skipped objectForKey:collectionPath] boolValue]
525                                                                                           hidden:[[hidden objectForKey:collectionPath] boolValue]
526                                                                                           manual:[custom containsObject:collectionPath]
527                                                                                        inContext:context];
528        }
529        //Set the mount values for all
530        [SapphireCollectionDirectory availableCollectionDirectoriesInContext:context];
531       
532        [display setCurrentFile:BRLocalizedString(@"Upgrading Movie Translations", @"")];
533        NSDictionary *movieTranslations = [NSDictionary dictionaryWithContentsOfFile:[configDir stringByAppendingPathComponent:@"movieData.plist"]];
534        NSDictionary *translations = [movieTranslations objectForKey:MOVIE_TRAN_TRANSLATIONS_KEY];
535        NSEnumerator *movieEnum = [translations keyEnumerator];
536        NSString *movie = nil;
537        while((movie = [movieEnum nextObject]) != nil)
538        {
539                NSDictionary *movieDict = [translations objectForKey:movie];
540                SapphireMovieTranslation *trans = [SapphireMovieTranslation createMovieTranslationWithName:movie inContext:context];
541                trans.IMPLink = [movieDict objectForKey:MOVIE_TRAN_IMP_LINK_KEY];
542                NSString *IMDBLink = [movieDict objectForKey:MOVIE_TRAN_IMDB_LINK_KEY];
543                trans.IMDBLink = IMDBLink;
544               
545                int imdbNumber = [SapphireMovie imdbNumberFromString:IMDBLink];
546                if(imdbNumber != 0)
547                {
548                        SapphireMovie *thisMovie = [SapphireMovie movieWithIMDB:imdbNumber inContext:context];
549                        trans.movie = thisMovie;
550                }
551               
552                NSArray *posters = [movieDict objectForKey:MOVIE_TRAN_IMP_POSTERS_KEY];
553                NSSet *dupCheck = [NSSet setWithArray:posters];
554                posters = [dupCheck allObjects];
555               
556                NSString *selectedPoster = [movieDict objectForKey:MOVIE_TRAN_SELECTED_POSTER_KEY];
557                int i, count = [posters count];
558                for(i=0; i<count; i++)
559                {
560                        NSString *posterUrl = [posters objectAtIndex:i];
561                        if([posterUrl isEqualToString:selectedPoster])
562                                trans.selectedPosterIndexValue = i;
563                       
564                        [SapphireMoviePoster createPosterWithLink:posterUrl index:i translation:trans inContext:context];
565                }
566        }
567       
568        [display setCurrentFile:BRLocalizedString(@"Upgrading TV Translations", @"")];
569        NSDictionary *tvTranslations = [NSDictionary dictionaryWithContentsOfFile:[configDir stringByAppendingPathComponent:@"tvdata.plist"]];
570        translations = [tvTranslations objectForKey:@"Translations"];
571        NSEnumerator *tvEnum = [translations keyEnumerator];
572        NSString *tvShow = nil;
573        while((tvShow = [tvEnum nextObject]) != nil)
574        {
575                NSString *showPath = [translations objectForKey:tvShow];
576                SapphireTVTranslation *trans = [SapphireTVTranslation createTVTranslationForName:tvShow withPath:showPath inContext:context];
577                SapphireTVShow *show = [SapphireTVShow showWithPath:showPath inContext:context];
578                trans.tvShow = show;
579        }
580       
581        NSError *error = nil;
582        NSManagedObject *obj;
583        NSEnumerator *objEnum = [[context registeredObjects] objectEnumerator];
584        while((obj = [objEnum nextObject]) != nil)
585        {
586                if(![obj validateForUpdate:&error])
587                        SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_INFO, @"%@", error);
588        }
589}
590
591+ (NSString *)collectionArtPath
592{
593        static NSString *path = nil;
594        if(path == nil)
595                path = [[applicationSupportDir() stringByAppendingPathComponent:@"Collection Art"] retain];
596        return path;
597}
598
599+ (NSDictionary *)changesDictionaryForContext:(NSManagedObjectContext *)moc
600{
601        NSSet *insertedObjects = [moc insertedObjects];
602        NSSet *deletedObjects = [moc deletedObjects];
603        NSMutableSet *updatedObjects = [[moc updatedObjects] mutableCopy];
604        [updatedObjects minusSet:insertedObjects];
605        [updatedObjects autorelease];
606       
607        NSManagedObject *obj;
608        NSEnumerator *objEnum = [insertedObjects objectEnumerator];
609        NSMutableDictionary *inserted = [NSMutableDictionary dictionary];
610        while((obj = [objEnum nextObject]) != nil)
611        {
612                [inserted setObject:[obj changedValuesWithObjectIDs] forKey:[[obj objectID] URIRepresentation]];
613        }
614        objEnum = [updatedObjects objectEnumerator];
615        NSMutableDictionary *updated = [NSMutableDictionary dictionary];
616        while((obj = [objEnum nextObject]) != nil)
617        {
618                NSDictionary *changes = [obj changedValuesWithObjectIDs];
619                if([changes count])
620                        [updated setObject:changes forKey:[[obj objectID] URIRepresentation]];
621        }
622        NSArray *deleted = [deletedObjects valueForKeyPath:@"objectID.URIRepresentation"];
623        return [NSDictionary dictionaryWithObjectsAndKeys:
624                        inserted, CHANGES_INSERTED_KEY,
625                        updated, CHANGES_UPDATED_KEY,
626                        deleted, CHANGES_DELETED_KEY,
627                        nil];
628}
629
630+ (void)applyChanges:(NSDictionary *)changes toContext:(NSManagedObjectContext *)moc
631{
632        NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
633        NSUndoManager *undo = [[NSUndoManager alloc] init];
634        [moc setUndoManager:undo];
635        [undo beginUndoGrouping];
636        BOOL failed = NO;
637        @try {
638                NSDictionary *inserted = [changes objectForKey:CHANGES_INSERTED_KEY];
639                NSDictionary *updated = [changes objectForKey:CHANGES_UPDATED_KEY];
640                NSArray *deleted = [changes objectForKey:CHANGES_DELETED_KEY];
641                NSMutableDictionary *objIDTranslation = [NSMutableDictionary dictionary];
642                NSURL *key;
643                NSEnumerator *keyEnum = [inserted keyEnumerator];
644                while((key = [keyEnum nextObject]) != nil && !failed)
645                {
646                        NSManagedObjectID *objId = [[moc persistentStoreCoordinator] managedObjectIDForURIRepresentation:key];
647                        NSEntityDescription *desc = [objId entity];
648                        NSManagedObject *newObj = [NSEntityDescription insertNewObjectForEntityForName:[desc name] inManagedObjectContext:moc];
649                        if(newObj == nil)
650                                failed = YES;
651                        else
652                                [objIDTranslation setObject:newObj forKey:key];
653                }
654                keyEnum = [inserted keyEnumerator];
655                while((key = [keyEnum nextObject]) != nil && !failed)
656                {
657                        NSManagedObject *obj = [objIDTranslation objectForKey:key];
658                        if(obj == nil)
659                                failed = YES;
660                        else
661                                [obj updateChanges:[inserted objectForKey:key] withTrans:objIDTranslation];
662                }
663                keyEnum = [updated keyEnumerator];
664                while((key = [keyEnum nextObject]) != nil && !failed)
665                {
666                        NSManagedObjectID *objId = [[moc persistentStoreCoordinator] managedObjectIDForURIRepresentation:key];
667                        if(objId == nil)
668                                failed = YES;
669                        else
670                        {
671                                NSManagedObject *obj = [moc objectWithID:objId];
672                                if(obj == nil)
673                                        failed = YES;
674                                else
675                                        [obj updateChanges:[updated objectForKey:key] withTrans:objIDTranslation];
676                        }
677                }
678                keyEnum = [deleted objectEnumerator];
679                while((key = [keyEnum nextObject]) != nil && !failed)
680                {
681                        NSManagedObjectID *objId = [[moc persistentStoreCoordinator] managedObjectIDForURIRepresentation:key];
682                        if(objId == nil)
683                                failed = YES;
684                        else
685                        {
686                                NSManagedObject *obj = [moc objectWithID:objId];
687                                if(obj == nil)
688                                        failed = YES;
689                                else
690                                        [moc deleteObject:obj];
691                        }
692                }               
693        }
694        @catch (NSException * e) {
695                [SapphireApplianceController logException:e];
696                failed = YES;
697        }
698        [undo endUndoGrouping];
699        if(failed)
700        {
701                SapphireLog(SAPPHIRE_LOG_METADATA_STORE, SAPPHIRE_LOG_LEVEL_ERROR, @"Apply failed for %@, undoing", changes);
702                [undo undo];
703        }
704        [moc setUndoManager:nil];
705        [undo release];
706        [pool release];
707}
708
709@end
Note: See TracBrowser for help on using the browser.